Monotone-Parent: 467dc56666485b20c968357bff135be6a192df0c

Monotone-Revision: 81ebf97febc5675193974e8834acc35e001494f1

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2007-03-18T15:20:38
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Wolfgang Sourdeau
2007-03-18 15:20:38 +00:00
parent 84bba82435
commit d26f7a625a
7 changed files with 982 additions and 1239 deletions
+335 -427
View File
@@ -1,458 +1,366 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
/* UIxAppointmentEditor.m - this file is part of SOGo
*
* Copyright (C) 2007 Inverse groupe conseil
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
This file is part of OpenGroupware.org.
#import <NGObjWeb/SoObject.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGExtensions/NSCalendarDate+misc.h>
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.
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalPerson.h>
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 <SoObjects/SOGo/AgenorUserManager.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoContentObject.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Appointments/SOGoAppointmentObject.h>
#import "UIxComponentEditor.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
- (id) init
{
[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];
if ((self = [super init]))
{
aptStartDate = nil;
aptEndDate = nil;
item = nil;
event = nil;
}
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;
calendar = [iCalCalendar parseSingleFromSource: _iCalString];
appointment = (iCalEvent *) [calendar firstChildWithTag: @"vevent"];
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
/* template values */
- (iCalEvent *) event
{
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;
return event;
}
- (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
- (NSString *) saveURL
{
return [NSString stringWithFormat: @"%@/saveAsAppointment",
[[self clientObject] baseURL]];
}
- (id) acceptAction
- (NSString *) _toolbarForCalObject
{
return [self acceptOrDeclineAction:YES];
SOGoUser *currentUser;
SOGoAppointmentObject *clientObject;
NSString *filename, *email;
iCalPerson *person;
iCalPersonPartStat participationStatus;
clientObject = [self clientObject];
currentUser = [[self context] activeUser];
email = [currentUser email];
if ([clientObject isOrganizer: email
orOwner: [currentUser login]])
filename = @"SOGoAppointmentObject.toolbar";
else
{
if ([clientObject isParticipant: email])
{
person = [[clientObject component: NO] findParticipantWithEmail: email];
participationStatus = [person participationStatus];
if (participationStatus == iCalPersonPartStatAccepted)
filename = @"SOGoAppointmentObjectDecline.toolbar";
else if (participationStatus == iCalPersonPartStatDeclined)
filename = @"SOGoAppointmentObjectAccept.toolbar";
else
filename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
}
else
filename = @"";
}
return filename;
}
- (id) declineAction
- (NSString *) toolbar
{
return [self acceptOrDeclineAction:NO];
return ([self _toolbarForCalObject]);
}
// TODO: add tentatively
- (id) acceptOrDeclineAction: (BOOL) _accept
/* icalendar values */
- (BOOL) isAllDay
{
// 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"]];
return NO;
}
@end /* UIxAppointmentEditor */
- (void) setIsAllDay: (BOOL) newIsAllDay
{
}
- (void) setAptStartDate: (NSCalendarDate *) newAptStartDate
{
ASSIGN (aptStartDate, newAptStartDate);
}
- (NSCalendarDate *) aptStartDate
{
return aptStartDate;
}
- (void) setAptEndDate: (NSCalendarDate *) newAptEndDate
{
ASSIGN (aptEndDate, newAptEndDate);
}
- (NSCalendarDate *) aptEndDate
{
return aptEndDate;
}
- (NSArray *) repeatList
{
static NSArray *repeatItems = nil;
if (!repeatItems)
{
repeatItems = [NSArray arrayWithObjects: @"DAILY",
@"WEEKLY",
@"BI-WEEKLY",
@"EVERY WEEKDAY",
@"MONTHLY",
@"YEARLY",
@"-",
@"CUSTOM",
nil];
[repeatItems retain];
}
return repeatItems;
}
- (NSString *) itemRepeatText
{
NSString *text;
if ([item isEqualToString: @"-"])
text = item;
else
text = [self labelForKey: [NSString stringWithFormat: @"repeat_%@", item]];
return text;
}
- (void) setItem: (NSString *) newItem
{
item = newItem;
}
- (NSString *) item
{
return item;
}
- (NSArray *) reminderList
{
static NSArray *reminderItems = nil;
if (!reminderItems)
{
reminderItems = [NSArray arrayWithObjects: @"5_MINUTES_BEFORE",
@"10_MINUTES_BEFORE",
@"15_MINUTES_BEFORE",
@"30_MINUTES_BEFORE",
@"45_MINUTES_BEFORE",
@"-",
@"1_HOUR_BEFORE",
@"2_HOURS_BEFORE",
@"5_HOURS_BEFORE",
@"15_HOURS_BEFORE",
@"-",
@"1_DAY_BEFORE",
@"2_DAYS_BEFORE",
@"1_WEEK_BEFORE",
@"-",
@"CUSTOM",
nil];
[reminderItems retain];
}
return reminderItems;
}
// - (void) setReminder: (NSString *) reminder
// {
// ASSIGN(reminder, _reminder);
// }
// - (NSString *) reminder
// {
// return reminder;
// }
- (NSString *) itemReminderText
{
NSString *text;
if ([item isEqualToString: @"-"])
text = item;
else
text = [self labelForKey: [NSString stringWithFormat: @"reminder_%@", item]];
return text;
}
- (NSString *) repeat
{
return @"";
}
- (void) setRepeat: (NSString *) newRepeat
{
}
- (NSString *) reminder
{
return @"";
}
- (void) setReminder: (NSString *) newReminder
{
}
/* actions */
- (NSCalendarDate *) newStartDate
{
NSCalendarDate *newStartDate, *now;
int hour;
newStartDate = [self selectedDate];
if ([[self queryParameterForKey: @"hm"] length] == 0)
{
now = [NSCalendarDate calendarDate];
[now setTimeZone: [[self clientObject] userTimeZone]];
if ([now isDateOnSameDay: newStartDate])
{
hour = [now hourOfDay];
if (hour < 8)
newStartDate = [now hour: 8 minute: 0];
else if (hour > 18)
newStartDate = [[now tomorrow] hour: 8 minute: 0];
else
newStartDate = now;
}
else
newStartDate = [newStartDate hour: 8 minute: 0];
}
return newStartDate;
}
- (id <WOActionResults>) defaultAction
{
NSCalendarDate *startDate, *endDate;
NSString *duration;
unsigned int minutes;
event = (iCalEvent *) [[self clientObject] component: NO];
if (event)
{
startDate = [event startDate];
endDate = [event endDate];
}
else
{
startDate = [self newStartDate];
duration = [self queryParameterForKey:@"dur"];
if ([duration length] > 0)
minutes = [duration intValue];
else
minutes = 60;
endDate
= [startDate dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: minutes seconds: 0];
}
ASSIGN (aptStartDate, startDate);
ASSIGN (aptEndDate, endDate);
/* here comes the code for initializing repeat, reminder and isAllDay... */
return self;
}
- (id <WOActionResults>) newAction
{
NSString *objectId, *method, *uri;
id <WOActionResults> result;
Class clientKlazz;
clientKlazz = [[self clientObject] class];
objectId = [clientKlazz globallyUniqueObjectId];
if ([objectId length] > 0)
{
method = [NSString stringWithFormat:@"%@/Calendar/%@/editAsAppointment",
[self userFolderPath], objectId];
uri = [self completeHrefForMethod: method];
result = [self redirectToLocation: uri];
}
else
result = [NSException exceptionWithHTTPStatus: 500 /* Internal Error */
reason: @"could not create a unique ID"];
return result;
}
- (id <WOActionResults>) saveAction
{
SOGoAppointmentObject *clientObject;
NSString *iCalString;
clientObject = [self clientObject];
iCalString = [[clientObject calendar: NO] versitString];
[clientObject saveContentString: iCalString];
return [self jsCloseWithRefreshMethod: @"refreshAppointmentsAndDisplay()"];
}
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
inContext: (WOContext*) context
{
return ([[self clientObject] isKindOfClass: [SOGoAppointmentObject class]]
&& [[request method] isEqualToString: @"POST"]);
}
- (void) takeValuesFromRequest: (WORequest *) _rq
inContext: (WOContext *) _ctx
{
SOGoAppointmentObject *clientObject;
clientObject = [self clientObject];
event = (iCalEvent *) [clientObject component: YES];
[super takeValuesFromRequest: _rq inContext: _ctx];
[event setStartDate: aptStartDate];
[event setEndDate: aptEndDate];
if ([clientObject isNew])
[event setTransparency: @"OPAQUE"];
}
@end