dc40718409
Monotone-Parent: 18c8420c0a0c752ea4dd761f934a473b321568f8 Monotone-Revision: b2699a981cd5458fbd1da1a9a72992020a29a4fc Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2006-12-14T21:20:13 Monotone-Branch: ca.inverse.sogo
461 lines
12 KiB
Objective-C
461 lines
12 KiB
Objective-C
/*
|
|
Copyright (C) 2004-2005 SKYRIX Software AG
|
|
|
|
This file is part of OpenGroupware.org.
|
|
|
|
OGo is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU Lesser General Public License as published by the
|
|
Free Software Foundation; either version 2, or (at your option) any
|
|
later version.
|
|
|
|
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with OGo; see the file COPYING. If not, write to the
|
|
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
*/
|
|
|
|
#import <NGCards/NSString+NGCards.h>
|
|
#import <NGCards/NSCalendarDate+NGCards.h>
|
|
|
|
#import <SOGo/AgenorUserManager.h>
|
|
#import <SOGo/NSCalendarDate+SOGo.h>
|
|
|
|
#import "common.h"
|
|
#import <NGCards/NGCards.h>
|
|
#import <NGExtensions/NGCalendarDateRange.h>
|
|
#import <SOGoUI/SOGoDateFormatter.h>
|
|
#import <Appointments/SOGoAppointmentFolder.h>
|
|
#import <Appointments/SOGoAppointmentObject.h>
|
|
#import "UIxComponent+Agenor.h"
|
|
|
|
#import "UIxAppointmentEditor.h"
|
|
|
|
/* TODO: CLEAN UP */
|
|
|
|
@implementation UIxAppointmentEditor
|
|
|
|
+ (int)version {
|
|
return [super version] + 0 /* v2 */;
|
|
}
|
|
|
|
+ (void)initialize {
|
|
NSAssert2([super version] == 2,
|
|
@"invalid superclass (%@) version %i !",
|
|
NSStringFromClass([self superclass]), [super version]);
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[endDate release];
|
|
[super dealloc];
|
|
}
|
|
|
|
/* accessors */
|
|
|
|
- (void) setAptStartDate: (NSCalendarDate *)_date
|
|
{
|
|
[self setStartDate: _date];
|
|
}
|
|
|
|
- (NSCalendarDate *) aptStartDate
|
|
{
|
|
return [self startDate];
|
|
}
|
|
|
|
- (void) setAptEndDate: (NSCalendarDate *) _date
|
|
{
|
|
ASSIGN(endDate, _date);
|
|
}
|
|
|
|
- (NSCalendarDate *) aptEndDate
|
|
{
|
|
return endDate;
|
|
}
|
|
|
|
/* transparency */
|
|
|
|
- (NSString *) transparency
|
|
{
|
|
return @"OPAQUE";
|
|
}
|
|
|
|
/* iCal */
|
|
|
|
- (NSString *)iCalStringTemplate {
|
|
static NSString *iCalStringTemplate = \
|
|
@"BEGIN:VCALENDAR\r\n"
|
|
@"METHOD:REQUEST\r\n"
|
|
@"PRODID://Inverse groupe conseil/SOGo 0.9\r\n"
|
|
@"VERSION:2.0\r\n"
|
|
@"BEGIN:VEVENT\r\n"
|
|
@"UID:%@\r\n"
|
|
@"CLASS:PUBLIC\r\n"
|
|
@"STATUS:CONFIRMED\r\n" /* confirmed by default */
|
|
@"DTSTAMP:%@Z\r\n"
|
|
@"DTSTART:%@Z\r\n"
|
|
@"DTEND:%@Z\r\n"
|
|
@"TRANSP:%@\r\n"
|
|
@"SEQUENCE:1\r\n"
|
|
@"PRIORITY:5\r\n"
|
|
@"%@" /* organizer */
|
|
@"%@" /* participants and resources */
|
|
@"END:VEVENT\r\n"
|
|
@"END:VCALENDAR";
|
|
|
|
NSTimeZone *utc;
|
|
NSCalendarDate *lStartDate, *lEndDate, *stamp;
|
|
NSString *template, *s;
|
|
unsigned minutes;
|
|
|
|
s = [self queryParameterForKey:@"dur"];
|
|
if ([s length] > 0)
|
|
minutes = [s intValue];
|
|
else
|
|
minutes = 60;
|
|
|
|
utc = [NSTimeZone timeZoneWithName: @"GMT"];
|
|
lStartDate = [self newStartDate];
|
|
[lStartDate setTimeZone: utc];
|
|
lEndDate = [lStartDate dateByAddingYears: 0 months: 0 days: 0
|
|
hours: 0 minutes: minutes seconds: 0];
|
|
|
|
stamp = [NSCalendarDate calendarDate];
|
|
[stamp setTimeZone: utc];
|
|
|
|
s = [self iCalParticipantsAndResourcesStringFromQueryParameters];
|
|
template = [NSString stringWithFormat:iCalStringTemplate,
|
|
[[self clientObject] nameInContainer],
|
|
[stamp iCalFormattedDateTimeString],
|
|
[lStartDate iCalFormattedDateTimeString],
|
|
[lEndDate iCalFormattedDateTimeString],
|
|
[self transparency],
|
|
[self iCalOrganizerString],
|
|
s];
|
|
return template;
|
|
}
|
|
|
|
/* new */
|
|
|
|
- (id) newAction
|
|
{
|
|
/*
|
|
This method creates a unique ID and redirects to the "edit" method on the
|
|
new ID.
|
|
It is actually a folder method and should be defined on the folder.
|
|
|
|
Note: 'clientObject' is the SOGoAppointmentFolder!
|
|
Update: remember that there are group folders as well.
|
|
*/
|
|
NSString *uri, *objectId, *method, *ps;
|
|
|
|
objectId = [NSClassFromString(@"SOGoAppointmentFolder")
|
|
globallyUniqueObjectId];
|
|
if ([objectId length] == 0) {
|
|
return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
|
|
reason:@"could not create a unique ID"];
|
|
}
|
|
|
|
method = [NSString stringWithFormat:@"Calendar/%@/editAsAppointment", objectId];
|
|
method = [[self userFolderPath] stringByAppendingPathComponent:method];
|
|
|
|
/* check if participants have already been provided */
|
|
ps = [self queryParameterForKey:@"ps"];
|
|
// if (ps) {
|
|
// [self setQueryParameter:ps forKey:@"ps"];
|
|
// }
|
|
if (!ps
|
|
&& [[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
|
|
AgenorUserManager *um;
|
|
NSArray *uids;
|
|
NSMutableArray *emails;
|
|
unsigned i, count;
|
|
|
|
/* add all current calendarUIDs as default participants */
|
|
|
|
um = [AgenorUserManager sharedUserManager];
|
|
uids = [[self clientObject] calendarUIDs];
|
|
count = [uids count];
|
|
emails = [NSMutableArray arrayWithCapacity:count];
|
|
|
|
for (i = 0; i < count; i++) {
|
|
NSString *email;
|
|
|
|
email = [um getEmailForUID:[uids objectAtIndex:i]];
|
|
if (email)
|
|
[emails addObject:email];
|
|
}
|
|
ps = [emails componentsJoinedByString:@","];
|
|
[self setQueryParameter:ps forKey:@"ps"];
|
|
}
|
|
uri = [self completeHrefForMethod:method];
|
|
return [self redirectToLocation:uri];
|
|
}
|
|
|
|
/* save */
|
|
|
|
- (void) loadValuesFromAppointment: (iCalEvent *) appointment
|
|
{
|
|
NSTimeZone *uTZ;
|
|
|
|
[self loadValuesFromComponent: appointment];
|
|
|
|
uTZ = [[self clientObject] userTimeZone];
|
|
endDate = [appointment endDate];
|
|
if (!endDate)
|
|
endDate = [[self startDate] dateByAddingYears: 0 months: 0 days: 0
|
|
hours: 1 minutes: 0 seconds: 0];
|
|
|
|
[endDate setTimeZone: uTZ];
|
|
[endDate retain];
|
|
}
|
|
|
|
- (void) saveValuesIntoAppointment: (iCalEvent *) _appointment
|
|
{
|
|
/* merge in form values */
|
|
NSArray *attendees, *lResources;
|
|
iCalRecurrenceRule *rrule;
|
|
|
|
[_appointment setStartDate:[self aptStartDate]];
|
|
[_appointment setEndDate:[self aptEndDate]];
|
|
|
|
[_appointment setSummary: [self title]];
|
|
[_appointment setUrl: [self url]];
|
|
[_appointment setLocation: [self location]];
|
|
[_appointment setComment: [self comment]];
|
|
[_appointment setPriority:[self priority]];
|
|
[_appointment setAccessClass: [self privacy]];
|
|
[_appointment setStatus: [self status]];
|
|
|
|
// [_appointment setCategories: [[self categories] componentsJoinedByString: @","]];
|
|
|
|
[_appointment setTransparency: [self transparency]];
|
|
|
|
#if 0
|
|
/*
|
|
Note: bad, bad, bad!
|
|
Organizer is no form value, thus we MUST NOT change it
|
|
*/
|
|
[_appointment setOrganizer:organizer];
|
|
#endif
|
|
attendees = [self participants];
|
|
lResources = [self resources];
|
|
if ([lResources count] > 0) {
|
|
attendees = ([attendees count] > 0)
|
|
? [attendees arrayByAddingObjectsFromArray: lResources]
|
|
: lResources;
|
|
}
|
|
[attendees makeObjectsPerformSelector: @selector (setTag:)
|
|
withObject: @"attendee"];
|
|
[_appointment setAttendees: attendees];
|
|
|
|
/* cycles */
|
|
[_appointment removeAllRecurrenceRules];
|
|
rrule = [self rrule];
|
|
if (rrule)
|
|
[_appointment addToRecurrenceRules: rrule];
|
|
}
|
|
|
|
- (iCalEvent *) appointmentFromString: (NSString *) _iCalString
|
|
{
|
|
iCalCalendar *calendar;
|
|
iCalEvent *appointment;
|
|
SOGoAppointmentObject *clientObject;
|
|
|
|
clientObject = [self clientObject];
|
|
calendar = [iCalCalendar parseSingleFromSource: _iCalString];
|
|
appointment = [clientObject firstEventFromCalendar: calendar];
|
|
|
|
return appointment;
|
|
}
|
|
|
|
/* conflict management */
|
|
|
|
- (BOOL) containsConflict: (id) _apt
|
|
{
|
|
NSArray *attendees, *uids;
|
|
SOGoAppointmentFolder *groupCalendar;
|
|
NSArray *infos;
|
|
NSArray *ranges;
|
|
id folder;
|
|
|
|
[self logWithFormat:@"search from %@ to %@",
|
|
[_apt startDate], [_apt endDate]];
|
|
|
|
folder = [[self clientObject] container];
|
|
attendees = [_apt attendees];
|
|
uids = [folder uidsFromICalPersons:attendees];
|
|
if ([uids count] == 0) {
|
|
[self logWithFormat:@"Note: no UIDs selected."];
|
|
return NO;
|
|
}
|
|
|
|
groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
|
|
inContext:[self context]];
|
|
[self debugWithFormat:@"group calendar: %@", groupCalendar];
|
|
|
|
if (![groupCalendar respondsToSelector:@selector(fetchFreeBusyInfosFrom:to:)]) {
|
|
[self errorWithFormat:@"invalid folder to run freebusy query on!"];
|
|
return NO;
|
|
}
|
|
|
|
infos = [groupCalendar fetchFreeBusyInfosFrom:[_apt startDate]
|
|
to:[_apt endDate]];
|
|
[self debugWithFormat:@" process: %d events", [infos count]];
|
|
|
|
ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey: @"startDate"
|
|
andEndDateKey: @"endDate"];
|
|
ranges = [ranges arrayByCompactingContainedDateRanges];
|
|
[self debugWithFormat:@" blocked ranges: %@", ranges];
|
|
|
|
return [ranges count] != 0 ? YES : NO;
|
|
}
|
|
|
|
/* actions */
|
|
|
|
- (id) testAction
|
|
{
|
|
/* for testing only */
|
|
WORequest *req;
|
|
iCalEvent *apt;
|
|
NSString *content;
|
|
|
|
req = [[self context] request];
|
|
apt = [self appointmentFromString: [self iCalString]];
|
|
[self saveValuesIntoAppointment:apt];
|
|
content = [[apt parent] versitString];
|
|
[self logWithFormat:@"%s -- iCal:\n%@",
|
|
__PRETTY_FUNCTION__,
|
|
content];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id<WOActionResults>) defaultAction
|
|
{
|
|
NSString *ical;
|
|
|
|
/* load iCalendar file */
|
|
|
|
// TODO: can't we use [clientObject contentAsString]?
|
|
// ical = [[self clientObject] valueForKey:@"iCalString"];
|
|
ical = [[self clientObject] contentAsString];
|
|
if ([ical length] == 0) /* a new appointment */
|
|
ical = [self iCalStringTemplate];
|
|
|
|
[self setICalString:ical];
|
|
[self loadValuesFromAppointment: [self appointmentFromString: ical]];
|
|
|
|
// if (![self canEditComponent]) {
|
|
// /* TODO: we need proper ACLs */
|
|
// return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
|
|
// }
|
|
return self;
|
|
}
|
|
|
|
- (id <WOActionResults>) saveAction
|
|
{
|
|
iCalEvent *apt;
|
|
iCalPerson *p;
|
|
id <WOActionResults> result;
|
|
NSString *content;
|
|
NSException *ex;
|
|
|
|
if (![self isWriteableClientObject]) {
|
|
/* return 400 == Bad Request */
|
|
return [NSException exceptionWithHTTPStatus:400
|
|
reason:@"method cannot be invoked on "
|
|
@"the specified object"];
|
|
}
|
|
|
|
apt = [self appointmentFromString: [self iCalString]];
|
|
if (apt == nil) {
|
|
NSString *s;
|
|
|
|
s = [self labelForKey:@"Invalid iCal data!"];
|
|
[self setErrorText:s];
|
|
return self;
|
|
}
|
|
|
|
[self saveValuesIntoAppointment:apt];
|
|
p = [apt findParticipantWithEmail:[self emailForUser]];
|
|
if (p) {
|
|
[p setParticipationStatus:iCalPersonPartStatAccepted];
|
|
}
|
|
|
|
if ([self checkForConflicts]) {
|
|
if ([self containsConflict:apt]) {
|
|
NSString *s;
|
|
|
|
s = [self labelForKey:@"Conflicts found!"];
|
|
[self setErrorText:s];
|
|
|
|
return self;
|
|
}
|
|
}
|
|
content = [[apt parent] versitString];
|
|
// [apt release]; apt = nil;
|
|
|
|
if (content == nil) {
|
|
NSString *s;
|
|
|
|
s = [self labelForKey:@"Could not create iCal data!"];
|
|
[self setErrorText:s];
|
|
return self;
|
|
}
|
|
|
|
ex = [[self clientObject] saveContentString:content];
|
|
if (ex != nil) {
|
|
[self setErrorText:[ex reason]];
|
|
return self;
|
|
}
|
|
|
|
if ([[[[self context] request] formValueForKey: @"nojs"] intValue])
|
|
result = [self redirectToLocation: [self applicationPath]];
|
|
else
|
|
result = [self jsCloseWithRefreshMethod: @"refreshAppointmentsAndDisplay()"];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSString *) saveUrl
|
|
{
|
|
return [NSString stringWithFormat: @"%@/saveAsAppointment",
|
|
[[self clientObject] baseURL]];
|
|
}
|
|
|
|
- (id) acceptAction
|
|
{
|
|
return [self acceptOrDeclineAction:YES];
|
|
}
|
|
|
|
- (id) declineAction
|
|
{
|
|
return [self acceptOrDeclineAction:NO];
|
|
}
|
|
|
|
// TODO: add tentatively
|
|
|
|
- (id) acceptOrDeclineAction: (BOOL) _accept
|
|
{
|
|
// TODO: this should live in the SoObjects
|
|
NSException *ex;
|
|
|
|
if ((ex = [self validateObjectForStatusChange]) != nil)
|
|
return ex;
|
|
|
|
ex = [[self clientObject] changeParticipationStatus:
|
|
_accept ? @"ACCEPTED" : @"DECLINED"
|
|
inContext:[self context]];
|
|
if (ex != nil) return ex;
|
|
|
|
return self;
|
|
// return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
|
|
}
|
|
|
|
@end /* UIxAppointmentEditor */
|