Monotone-Parent: 467dc56666485b20c968357bff135be6a192df0c

Monotone-Revision: 81ebf97febc5675193974e8834acc35e001494f1

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2007-03-18T15:20:38
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2007-03-18 15:20:38 +00:00
parent 84bba82435
commit d26f7a625a
7 changed files with 982 additions and 1239 deletions

View File

@ -1,5 +1,14 @@
2007-03-18 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Scheduler/UIxComponentEditor.m: no longer a superclass for
the task and appointment editors. Now a container component
handling the common elements of tasks and events.
* UI/Scheduler/UIxTaskEditor.m: same as below.
* UI/Scheduler/UIxAppointmentEditor.m: rewrote template. No longer
a subclass of UIxComponentEditor but a subcomponent of it.
* UI/Common/UIxToolbar.m ([UIxToolbar -toolbarConfig]): the
toolbar can have the special value "none" to indicate there is
none attached to the window.

View File

@ -1,6 +1,6 @@
/* UIxAppointmentEditor.h - this file is part of SOGo
*
* Copyright (C) 2006 Inverse groupe conseil
* Copyright (C) 2007 Inverse groupe conseil
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@ -23,50 +23,38 @@
#ifndef UIXAPPOINTMENTEDITOR_H
#define UIXAPPOINTMENTEDITOR_H
#import <UIxComponentEditor.h>
#import <SOGoUI/UIxComponent.h>
@class iCalEvent;
@class NSString;
@class iCalPerson;
@class iCalRecurrenceRule;
@interface UIxAppointmentEditor : UIxComponentEditor
@interface UIxAppointmentEditor : UIxComponent
{
NSCalendarDate *endDate;
iCalEvent *event;
NSCalendarDate *aptStartDate;
NSCalendarDate *aptEndDate;
NSString *item;
}
/* template values */
- (NSString *) saveURL;
- (iCalEvent *) event;
/* icalendar values */
- (BOOL) isAllDay;
- (void) setIsAllDay: (BOOL) newIsAllDay;
- (void) setAptStartDate: (NSCalendarDate *) _date;
- (NSCalendarDate *) aptStartDate;
- (void) setAptEndDate: (NSCalendarDate *) _date;
- (NSCalendarDate *) aptEndDate;
/* iCal */
- (NSString *) repeat;
- (void) setRepeat: (NSString *) newRepeat;
- (NSString *) iCalStringTemplate;
/* new */
- (id) newAction;
/* save */
- (void) loadValuesFromAppointment: (iCalEvent *) _apt;
- (void) saveValuesIntoAppointment: (iCalEvent *) _apt;
- (iCalEvent *) appointmentFromString: (NSString *) _iCalString;
/* conflict management */
- (BOOL) containsConflict: (id) _apt;
- (id <WOActionResults>) defaultAction;
- (id <WOActionResults>) saveAction;
- (id) acceptAction;
- (id) declineAction;
- (NSString *) saveUrl;
// TODO: add tentatively
- (id) acceptOrDeclineAction: (BOOL) _accept;
- (NSString *) reminder;
- (void) setReminder: (NSString *) newReminder;
@end

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

View File

@ -37,33 +37,36 @@
@interface UIxComponentEditor : UIxComponent
{
NSString *iCalString;
NSString *errorText;
iCalRepeatableEntityObject *component;
id item;
NSString *saveURL;
/* individual values */
NSCalendarDate *startDate;
NSCalendarDate *cycleUntilDate;
NSString *title;
NSString *location;
NSString *comment;
NSString *url;
iCalPerson *organizer;
NSArray *participants; /* array of iCalPerson's */
NSArray *resources; /* array of iCalPerson's */
NSString *priority;
NSString *privacy;
NSString *status;
NSArray *categories;
BOOL checkForConflicts; /* default: NO */
NSDictionary *cycle;
NSString *cycleEnd;
NSString *componentOwner;
BOOL componentLoaded;
NSString *attendeesNames;
NSString *attendeesEmails;
}
- (NSArray *) categoryItems;
- (void) setComponent: (iCalRepeatableEntityObject *) newComponent;
- (void) setSaveURL: (NSString *) newSaveURL;
- (NSString *) saveURL;
- (NSArray *) categoryList;
- (void) setCategories: (NSArray *) _categories;
- (NSArray *) categories;
- (NSString *) itemCategoryText;
@ -87,18 +90,6 @@
- (id) item;
- (NSString *) itemPriorityText;
- (void) setErrorText: (NSString *) _txt;
- (NSString *) errorText;
- (BOOL) hasErrorText;
- (void) setICalString: (NSString *) _s;
- (NSString *) iCalString;
- (NSCalendarDate *) newStartDate;
- (void) setStartDate: (NSCalendarDate *) _date;
- (NSCalendarDate *) startDate;
- (void) setTitle: (NSString *) _value;
- (NSString *) title;
@ -111,14 +102,11 @@
- (void) setUrl: (NSString *) _url;
- (NSString *) url;
- (void) setParticipants: (NSArray *) _parts;
- (NSArray *) participants;
- (void) setAttendeesNames: (NSString *) newAttendeesNames;
- (NSString *) attendeesNames;
- (void) setResources: (NSArray *) _res;
- (NSArray *) resources;
- (void) setCheckForConflicts: (BOOL) _checkForConflicts;
- (BOOL) checkForConflicts;
- (void) setAttendeesEmails: (NSString *) newAttendeesEmails;
- (NSString *) attendeesEmails;
- (NSArray *) cycles;
- (void) setCycle: (NSDictionary *) _cycle;
@ -150,17 +138,12 @@
- (BOOL) isWriteableClientObject;
- (NSException *) validateObjectForStatusChange;
/* subclasses */
- (void) loadValuesFromComponent: (iCalRepeatableEntityObject *) component;
- (NSString *) iCalStringTemplate;
- (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters;
- (NSString *) iCalParticipantsStringFromQueryParameters;
- (NSString *) iCalResourcesStringFromQueryParameters;
- (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
format: (NSString *) _format;
- (NSString *) iCalOrganizerString;
- (NSString *) toolbar;
@end

View File

@ -55,11 +55,12 @@
{
if ((self = [super init]))
{
component = nil;
[self setPrivacy: @"PUBLIC"];
[self setCheckForConflicts: NO];
[self setIsCycleEndNever];
componentOwner = @"";
componentLoaded = NO;
attendeesNames = nil;
attendeesEmails = nil;
}
return self;
@ -67,31 +68,102 @@
- (void) dealloc
{
[iCalString release];
[errorText release];
[item release];
[startDate release];
[cycleUntilDate release];
[title release];
[location release];
[organizer release];
[comment release];
[participants release];
[resources release];
[priority release];
[categories release];
[cycle release];
[cycleEnd release];
[url release];
[attendeesNames release];
[attendeesEmails release];
[super dealloc];
}
- (void) _loadAttendees
{
NSEnumerator *attendees;
iCalPerson *currentAttendee;
NSMutableString *names, *emails;
names = [NSMutableString new];
emails = [NSMutableString new];
attendees = [[component attendees] objectEnumerator];
currentAttendee = [attendees nextObject];
while (currentAttendee)
{
NSLog (@"currentCN: %@", [currentAttendee cn]);
[names appendFormat: @"%@,", [currentAttendee cn]];
[emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
currentAttendee = [attendees nextObject];
}
if ([names length] > 0)
{
ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
ASSIGN (attendeesEmails,
[emails substringToIndex: [emails length] - 1]);
}
[names release];
[emails release];
}
/* warning: we use this method which will be triggered by the template system
when the page is instantiated, but we should find another and cleaner way of
doing this... for example, when the clientObject is set */
- (void) setComponent: (iCalRepeatableEntityObject *) newComponent
{
// iCalRecurrenceRule *rrule;
SOGoObject *co;
if (!component)
{
component = newComponent;
co = [self clientObject];
componentOwner = [co ownerInContext: nil];
ASSIGN (title, [component summary]);
ASSIGN (location, [component location]);
ASSIGN (comment, [component comment]);
ASSIGN (url, [[component url] absoluteString]);
ASSIGN (privacy, [component accessClass]);
ASSIGN (priority, [component priority]);
ASSIGN (status, [component status]);
ASSIGN (categories, [[component categories] commaSeparatedValues]);
ASSIGN (organizer, [component organizer]);
[self _loadAttendees];
}
// /* cycles */
// if ([component isRecurrent])
// {
// rrule = [[component recurrenceRules] objectAtIndex: 0];
// [self adjustCycleControlsForRRule: rrule];
// }
}
- (void) setSaveURL: (NSString *) newSaveURL
{
saveURL = newSaveURL;
}
- (NSString *) saveURL
{
return saveURL;
}
/* accessors */
- (void) setItem: (id) _item
{
ASSIGN(item, _item);
ASSIGN (item, _item);
}
- (id) item
@ -114,34 +186,9 @@
return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
}
- (void) setErrorText: (NSString *) _txt
{
ASSIGNCOPY(errorText, _txt);
}
- (NSString *) errorText
{
return errorText;
}
- (BOOL) hasErrorText
{
return [errorText length] > 0 ? YES : NO;
}
- (void) setStartDate: (NSCalendarDate *) _date
{
ASSIGN(startDate, _date);
}
- (NSCalendarDate *) startDate
{
return startDate;
}
- (void) setTitle: (NSString *) _value
{
ASSIGNCOPY(title, _value);
ASSIGN (title, _value);
}
- (NSString *) title
@ -151,7 +198,7 @@
- (void) setUrl: (NSString *) _url
{
ASSIGNCOPY(url, _url);
ASSIGN (url, _url);
}
- (NSString *) url
@ -159,9 +206,29 @@
return url;
}
- (void) setAttendeesNames: (NSString *) newAttendeesNames
{
ASSIGN (attendeesNames, newAttendeesNames);
}
- (NSString *) attendeesNames
{
return attendeesNames;
}
- (void) setAttendeesEmails: (NSString *) newAttendeesEmails
{
ASSIGN (attendeesEmails, newAttendeesEmails);
}
- (NSString *) attendeesEmails
{
return attendeesEmails;
}
- (void) setLocation: (NSString *) _value
{
ASSIGNCOPY(location, _value);
ASSIGN (location, _value);
}
- (NSString *) location
@ -171,7 +238,7 @@
- (void) setComment: (NSString *) _value
{
ASSIGNCOPY(comment, _value);
ASSIGN (comment, _value);
}
- (NSString *) comment
@ -179,28 +246,34 @@
return comment;
}
- (NSArray *) categoryItems
- (NSArray *) categoryList
{
// TODO: make this configurable?
/*
Tasks categories will be modified as follow :
by default (a simple logo or no logo at all),
task,
outside,
meeting,
holidays,
phone.
*/
static NSArray *categoryItems = nil;
if (!categoryItems)
{
categoryItems = [NSArray arrayWithObjects: @"APPOINTMENT",
@"NOT IN OFFICE",
@"MEETING",
@"HOLIDAY",
@"PHONE CALL",
nil];
categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
@"BIRTHDAY",
@"BUSINESS",
@"CALLS",
@"CLIENTS",
@"COMPETITION",
@"CUSTOMER",
@"FAVORITES",
@"FOLLOW UP",
@"GIFTS",
@"HOLIDAYS",
@"IDEAS",
@"ISSUES",
@"MISCELLANEOUS",
@"PERSONAL",
@"PROJECTS",
@"PUBLIC HOLIDAY",
@"STATUS",
@"SUPPLIERS",
@"TRAVEL",
@"VACATION",
nil];
[categoryItems retain];
}
@ -209,7 +282,7 @@
- (void) setCategories: (NSArray *) _categories
{
ASSIGN(categories, _categories);
ASSIGN (categories, _categories);
}
- (NSArray *) categories
@ -219,7 +292,7 @@
- (NSString *) itemCategoryText
{
return [[self labelForKey: item] stringByEscapingHTMLString];
return [self labelForKey: [NSString stringWithFormat: @"category_%@", item]];
}
/* priorities */
@ -234,7 +307,7 @@
if (!priorities)
{
priorities = [NSArray arrayWithObjects:@"0", @"5", @"1", nil];
priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
[priorities retain];
}
@ -243,7 +316,7 @@
- (void) setPriority: (NSString *) _priority
{
ASSIGN(priority, _priority);
ASSIGN (priority, _priority);
}
- (NSString *) priority
@ -267,7 +340,7 @@
- (void) setPrivacy: (NSString *) _privacy
{
ASSIGN(privacy, _privacy);
ASSIGN (privacy, _privacy);
}
- (NSString *) privacy
@ -290,7 +363,7 @@
- (void) setStatus: (NSString *) _status
{
ASSIGN(status, _status);
ASSIGN (status, _status);
}
- (NSString *) status
@ -298,36 +371,6 @@
return status;
}
- (void) setParticipants: (NSArray *) _parts
{
ASSIGN(participants, _parts);
}
- (NSArray *) participants
{
return participants;
}
- (void) setResources: (NSArray *) _res
{
ASSIGN(resources, _res);
}
- (NSArray *) resources
{
return resources;
}
- (void) setCheckForConflicts: (BOOL) _checkForConflicts
{
checkForConflicts = _checkForConflicts;
}
- (BOOL) checkForConflicts
{
return checkForConflicts;
}
- (NSArray *) cycles
{
NSBundle *bundle;
@ -337,7 +380,7 @@
if (!cycles)
{
bundle = [NSBundle bundleForClass:[self class]];
path = [bundle pathForResource:@"cycles" ofType:@"plist"];
path = [bundle pathForResource: @"cycles" ofType: @"plist"];
NSAssert(path != nil, @"Cannot find cycles.plist!");
cycles = [[NSArray arrayWithContentsOfFile:path] retain];
NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
@ -348,7 +391,7 @@
- (void) setCycle: (NSDictionary *) _cycle
{
ASSIGN(cycle, _cycle);
ASSIGN (cycle, _cycle);
}
- (NSDictionary *) cycle
@ -365,21 +408,21 @@
{
NSString *key;
key = [(NSDictionary *)item objectForKey:@"label"];
key = [(NSDictionary *)item objectForKey: @"label"];
return [self labelForKey:key];
}
- (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
{
NSCalendarDate *until;
// NSCalendarDate *until;
/* copy hour/minute/second from startDate */
until = [_cycleUntilDate hour: [startDate hourOfDay]
minute: [startDate minuteOfHour]
second: [startDate secondOfMinute]];
[until setTimeZone: [startDate timeZone]];
ASSIGN(cycleUntilDate, until);
// /* copy hour/minute/second from startDate */
// until = [_cycleUntilDate hour: [startDate hourOfDay]
// minute: [startDate minuteOfHour]
// second: [startDate secondOfMinute]];
// [until setTimeZone: [startDate timeZone]];
// ASSIGN (cycleUntilDate, until);
}
- (NSCalendarDate *) cycleUntilDate
@ -394,7 +437,7 @@
if (![self hasCycle])
return nil;
ruleRep = [cycle objectForKey:@"rule"];
ruleRep = [cycle objectForKey: @"rule"];
rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
if (cycleUntilDate && [self isCycleEndUntil])
@ -405,20 +448,20 @@
- (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
{
NSDictionary *c;
NSCalendarDate *until;
// NSDictionary *c;
// NSCalendarDate *until;
c = [self cycleMatchingRRule:_rrule];
[self setCycle:c];
// c = [self cycleMatchingRRule:_rrule];
// [self setCycle:c];
until = [[[_rrule untilDate] copy] autorelease];
if (!until)
until = startDate;
else
[self setIsCycleEndUntil];
// until = [[[_rrule untilDate] copy] autorelease];
// if (!until)
// until = startDate;
// else
// [self setIsCycleEndUntil];
[until setTimeZone:[[self clientObject] userTimeZone]];
[self setCycleUntilDate:until];
// [until setTimeZone:[[self clientObject] userTimeZone]];
// [self setCycleUntilDate:until];
}
/*
@ -448,11 +491,11 @@
NSString *cr;
c = [cycles objectAtIndex:i];
cr = [c objectForKey:@"rule"];
cr = [c objectForKey: @"rule"];
if ([cr isEqualToString:cycleRep])
return c;
}
[self warnWithFormat:@"No default cycle for rrule found! -> %@", _rrule];
[self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
return nil;
}
@ -473,7 +516,7 @@
- (void) setCycleEnd: (NSString *) _cycleEnd
{
ASSIGNCOPY(cycleEnd, _cycleEnd);
ASSIGN (cycleEnd, _cycleEnd);
}
- (NSString *) cycleEnd
@ -483,18 +526,17 @@
- (BOOL) isCycleEndUntil
{
return (cycleEnd &&
[cycleEnd isEqualToString:@"cycle_end_until"]);
return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
}
- (void) setIsCycleEndUntil
{
[self setCycleEnd:@"cycle_end_until"];
[self setCycleEnd: @"cycle_end_until"];
}
- (void) setIsCycleEndNever
{
[self setCycleEnd:@"cycle_end_never"];
[self setCycleEnd: @"cycle_end_never"];
}
/* helpers */
@ -517,13 +559,13 @@
uri = [[[self context] request] uri];
/* first: identify query parameters */
r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
if (r.length > 0)
uri = [uri substringToIndex:r.location];
/* next: append trailing slash */
if (![uri hasSuffix:@"/"])
uri = [uri stringByAppendingString:@"/"];
if (![uri hasSuffix: @"/"])
uri = [uri stringByAppendingString: @"/"];
/* next: append method */
uri = [uri stringByAppendingString:_method];
@ -535,13 +577,7 @@
- (BOOL) isWriteableClientObject
{
return [[self clientObject]
respondsToSelector:@selector(saveContentString:)];
}
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) _rq
inContext: (WOContext*) _c
{
return YES;
respondsToSelector: @selector(saveContentString:)];
}
- (BOOL) containsConflict: (id) _component
@ -593,81 +629,6 @@
: @"visibility: hidden;");
}
/* subclasses */
- (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;
}
- (void) loadValuesFromComponent: (iCalRepeatableEntityObject *) component
{
iCalRecurrenceRule *rrule;
NSTimeZone *uTZ;
SOGoObject *co;
co = [self clientObject];
componentOwner = [co ownerInContext: nil];
componentLoaded = YES;
startDate = [component startDate];
// if ((startDate = [component startDate]) == nil)
// startDate = [[NSCalendarDate date] hour:11 minute:0];
uTZ = [co userTimeZone];
if (startDate)
{
[startDate setTimeZone: uTZ];
[startDate retain];
}
title = [[component summary] copy];
location = [[component location] copy];
comment = [[component comment] copy];
url = [[[component url] absoluteString] copy];
privacy = [[component accessClass] copy];
priority = [[component priority] copy];
status = [[component status] copy];
categories = [[[component categories] commaSeparatedValues] retain];
organizer = [[component organizer] retain];
participants = [[component participants] retain];
resources = [[component resources] retain];
/* cycles */
if ([component isRecurrent])
{
rrule = [[component recurrenceRules] objectAtIndex: 0];
[self adjustCycleControlsForRRule: rrule];
}
}
- (NSString *) iCalStringTemplate
{
[self subclassResponsibility: _cmd];
return @"";
}
- (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
{
NSString *s;
@ -709,7 +670,7 @@
NSArray *es;
unsigned i, count;
es = [s componentsSeparatedByString:@","];
es = [s componentsSeparatedByString: @","];
count = [es count];
for(i = 0; i < count; i++) {
NSString *email, *cn;
@ -728,13 +689,6 @@
[self cnForUser], [self emailForUser]];
}
- (NSString *) saveUrl
{
[self subclassResponsibility: _cmd];
return @"";
}
- (NSException *) validateObjectForStatusChange
{
id co;
@ -751,16 +705,6 @@
/* contact editor compatibility */
- (void) setICalString: (NSString *) _s
{
ASSIGNCOPY(iCalString, _s);
}
- (NSString *) iCalString
{
return iCalString;
}
- (NSArray *) availableCalendars
{
NSEnumerator *rawContacts;
@ -807,58 +751,58 @@
return classes;
}
- (NSString *) _toolbarForCalObject: (iCalEntityObject *) calObject
- (void) _handleAttendeesEdition
{
NSString *filename, *myEmail;
iCalPerson *person;
NSEnumerator *persons;
iCalPersonPartStat myParticipationStatus;
BOOL found;
NSArray *names, *emails;
NSMutableArray *newAttendees;
unsigned int count, max;
NSString *currentEmail;
iCalPerson *currentAttendee;
myEmail = [[[self context] activeUser] email];
if ([[organizer rfc822Email] isEqualToString: myEmail])
filename = @"SOGoAppointmentObject.toolbar";
else
newAttendees = [NSMutableArray new];
names = [attendeesNames componentsSeparatedByString: @","];
emails = [attendeesEmails componentsSeparatedByString: @","];
max = [emails count];
for (count = 0; count < max; count++)
{
filename = @"";
found = NO;
persons = [participants objectEnumerator];
person = [persons nextObject];
while (person && !found)
if ([[person rfc822Email] isEqualToString: myEmail])
{
found = YES;
myParticipationStatus = [person participationStatus];
if (myParticipationStatus == iCalPersonPartStatAccepted)
filename = @"SOGoAppointmentObjectDecline.toolbar";
else if (myParticipationStatus == iCalPersonPartStatDeclined)
filename = @"SOGoAppointmentObjectAccept.toolbar";
else
filename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
}
else
person = [persons nextObject];
currentEmail = [emails objectAtIndex: count];
currentAttendee = [component findParticipantWithEmail: currentEmail];
if (!currentAttendee)
{
currentAttendee = [iCalPerson elementWithTag: @"attendee"];
[currentAttendee setCn: [names objectAtIndex: count]];
[currentAttendee setEmail: currentEmail];
[currentAttendee setRole: @"REQ-PARTICIPANT"];
[currentAttendee
setParticipationStatus: iCalPersonPartStatNeedsAction];
}
[newAttendees addObject: currentAttendee];
}
return filename;
[component setAttendees: newAttendees];
[newAttendees release];
}
- (NSString *) toolbar
- (void) takeValuesFromRequest: (WORequest *) _rq
inContext: (WOContext *) _ctx
{
NSString *filename;
iCalEntityObject *calObject;
SOGoCalendarComponent *co;
NSCalendarDate *now;
if (componentLoaded)
[super takeValuesFromRequest: _rq inContext: _ctx];
now = [NSCalendarDate calendarDate];
[component setSummary: title];
[component setLocation: location];
[component setComment: comment];
[component setUrl: url];
if ([[self clientObject] isNew])
{
co = [self clientObject];
calObject = [co component];
filename = [self _toolbarForCalObject: calObject];
[component setCreated: now];
[component setTimeStampAsDate: now];
[component setPriority: @"0"];
}
else
filename = @"";
return filename;
[component setLastModified: now];
[self _handleAttendeesEdition];
}
@end

View File

@ -1,6 +1,6 @@
/* UIxTaskEditor.h - this file is part of SOGo
*
* Copyright (C) 2006 Inverse groupe conseil
* Copyright (C) 2007 Inverse groupe conseil
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@ -20,59 +20,41 @@
* Boston, MA 02111-1307, USA.
*/
#ifndef UIXTASKEDITOR_H
#define UIXTASKEDITOR_H
#ifndef UIXAPPOINTMENTEDITOR_H
#define UIXAPPOINTMENTEDITOR_H
#import "UIxComponentEditor.h"
#import <SOGoUI/UIxComponent.h>
@class NSString;
@class iCalPerson;
@class iCalRecurrenceRule;
@class iCalToDo;
@class NSString;
@interface UIxTaskEditor : UIxComponentEditor
@interface UIxTaskEditor : UIxComponent
{
NSCalendarDate *dueDate;
iCalToDo *todo;
NSCalendarDate *taskStartDate;
NSCalendarDate *taskDueDate;
NSCalendarDate *statusDate;
NSString *status;
NSString *statusPercent;
BOOL hasStartDate;
BOOL hasDueDate;
BOOL newTask;
NSString *item;
}
/* template values */
- (NSString *) saveURL;
- (iCalToDo *) todo;
/* icalendar values */
- (void) setTaskStartDate: (NSCalendarDate *) _date;
- (NSCalendarDate *) taskStartDate;
- (void) setTaskDueDate: (NSCalendarDate *) _date;
- (NSCalendarDate *) taskDueDate;
/* iCal */
- (NSString *) iCalStringTemplate;
/* new */
- (id) newAction;
/* save */
- (void) loadValuesFromTask: (iCalToDo *) _task;
- (void) saveValuesIntoTask: (iCalToDo *) _task;
- (iCalToDo *) taskFromString: (NSString *) _iCalString;
/* conflict management */
- (BOOL) containsConflict: (id) _task;
- (id <WOActionResults>) defaultAction;
- (id <WOActionResults>) saveAction;
- (id) changeStatusAction;
- (id) acceptAction;
- (id) declineAction;
- (NSString *) saveUrl;
// TODO: add tentatively
- (id) acceptOrDeclineAction: (BOOL) _accept;
- (NSString *) repeat;
- (void) setRepeat: (NSString *) newRepeat;
@end
#endif /* UIXTASKEDITOR_H */
#endif /* UIXAPPOINTMENTEDITOR_H */

View File

@ -1,503 +1,432 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
/* UIxTaskEditor.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/iCalToDo.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 <SOGo/NSCalendarDate+SOGo.h>
#import <SoObjects/SOGo/AgenorUserManager.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoContentObject.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Appointments/SOGoTaskObject.h>
#import "UIxComponentEditor.h"
#import "UIxTaskEditor.h"
/* TODO: CLEAN UP */
#import "common.h"
#import <NGCards/NGCards.h>
#import <NGExtensions/NGCalendarDateRange.h>
#import <SOGoUI/SOGoDateFormatter.h>
#import <SOGo/AgenorUserManager.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoTaskObject.h>
#import "UIxComponent+Agenor.h"
@implementation UIxTaskEditor
- (void) dealloc
- (id) init
{
[dueDate release];
[super dealloc];
}
- (void) setTaskStartDate: (NSCalendarDate *) _date
{
[self setStartDate: _date];
}
- (NSCalendarDate *) taskStartDate
{
return [self startDate];
}
- (void) setTaskDueDate: (NSCalendarDate *) _date
{
ASSIGN(dueDate, _date);
}
- (NSCalendarDate *) taskDueDate
{
return dueDate;
}
/* 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:VTODO\r\n"
@"UID:%@\r\n"
@"CLASS:PUBLIC\r\n"
@"STATUS:NEEDS-ACTION\r\n" /* confirmed by default */
@"PERCENT-COMPLETE:0\r\n"
@"DTSTART:%@Z\r\n"
@"DUE:%@Z\r\n"
@"DTSTAMP:%@Z\r\n"
@"SEQUENCE:1\r\n"
@"PRIORITY:5\r\n"
@"%@" /* organizer */
@"%@" /* participants and resources */
@"END:VTODO\r\n"
@"END:VCALENDAR";
NSCalendarDate *stamp, *lStartDate, *lDueDate;
NSString *template, *s;
NSTimeZone *utc;
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];
lDueDate = [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],
[lStartDate iCalFormattedDateTimeString],
[lDueDate iCalFormattedDateTimeString],
[stamp iCalFormattedDateTimeString],
[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/%@/editAsTask", 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) loadValuesFromTask: (iCalToDo *) _task
{
NSTimeZone *uTZ;
[self loadValuesFromComponent: _task];
uTZ = [[self clientObject] userTimeZone];
dueDate = [_task due];
// if (!dueDate)
// dueDate = [[self startDate] dateByAddingYears: 0 months: 0 days: 0
// hours: 1 minutes: 0 seconds: 0];
if (dueDate)
if ((self = [super init]))
{
[dueDate setTimeZone: uTZ];
[dueDate retain];
taskStartDate = nil;
taskDueDate = nil;
statusDate = nil;
hasStartDate = NO;
hasDueDate = NO;
status = nil;
statusPercent = nil;
item = nil;
todo = nil;
}
}
- (void) saveValuesIntoTask: (iCalToDo *) _task
{
/* merge in form values */
NSArray *attendees, *lResources;
iCalRecurrenceRule *rrule;
NSCalendarDate *dateTime;
if (hasStartDate)
dateTime = [self taskStartDate];
else
dateTime = nil;
[_task setStartDate: dateTime];
if (hasDueDate)
dateTime = [self taskDueDate];
else
dateTime = nil;
[_task setDue: dateTime];
[_task setSummary: [self title]];
[_task setUrl: [self url]];
[_task setLocation: [self location]];
[_task setComment: [self comment]];
[_task setPriority:[self priority]];
[_task setAccessClass: [self privacy]];
[_task setStatus: [self status]];
// [_task setCategories: [[self categories] componentsJoinedByString: @","]];
#if 0
/*
Note: bad, bad, bad!
Organizer is no form value, thus we MUST NOT change it
*/
[_task 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"];
[_task setAttendees:attendees];
/* cycles */
[_task removeAllRecurrenceRules];
rrule = [self rrule];
if (rrule)
[_task addToRecurrenceRules: rrule];
}
- (iCalToDo *) taskFromString: (NSString *) _iCalString
{
iCalCalendar *calendar;
iCalToDo *task;
calendar = [iCalCalendar parseSingleFromSource: _iCalString];
task = (iCalToDo *) [calendar firstChildWithTag: @"vtodo"];
return task;
}
/* conflict management */
- (BOOL) containsConflict: (id) _task
{
NSArray *attendees, *uids;
SOGoAppointmentFolder *groupCalendar;
NSArray *infos;
NSArray *ranges;
id folder;
[self logWithFormat:@"search from %@ to %@",
[_task startDate], [_task due]];
folder = [[self clientObject] container];
attendees = [_task 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:[_task startDate]
to:[_task due]];
[self debugWithFormat:@" process: %d tasks", [infos count]];
ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
andEndDateKey:@"dueDate"];
ranges = [ranges arrayByCompactingContainedDateRanges];
[self debugWithFormat:@" blocked ranges: %@", ranges];
return [ranges count] != 0 ? YES : NO;
}
- (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)
{
newTask = YES;
ical = [self iCalStringTemplate];
}
else
newTask = NO;
[self setICalString:ical];
[self loadValuesFromTask: [self taskFromString: ical]];
// if (![self canEditComponent]) {
// /* TODO: we need proper ACLs */
// return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
// }
return self;
}
- (id <WOActionResults>) saveAction
- (void) dealloc
{
iCalToDo *task;
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"];
}
task = [self taskFromString: [self iCalString]];
if (task == nil) {
NSString *s;
s = [self labelForKey: @"Invalid iCal data!"];
[self setErrorText: s];
return self;
}
[self saveValuesIntoTask:task];
p = [task findParticipantWithEmail:[self emailForUser]];
if (p) {
[p setParticipationStatus:iCalPersonPartStatAccepted];
}
if ([self checkForConflicts]) {
if ([self containsConflict:task]) {
NSString *s;
s = [self labelForKey:@"Conflicts found!"];
[self setErrorText:s];
return self;
}
}
content = [[task parent] versitString];
// [task release]; task = 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: @"refreshTasks()"];
return result;
[taskStartDate release];
[taskDueDate release];
[statusDate release];
[status release];
[statusPercent release];
[super dealloc];
}
- (id) changeStatusAction
/* template values */
- (iCalToDo *) todo
{
iCalToDo *task;
SOGoTaskObject *taskObject;
NSString *content;
id ex;
int newStatus;
newStatus = [[self queryParameterForKey: @"status"] intValue];
taskObject = [self clientObject];
task = (iCalToDo *) [taskObject component];
switch (newStatus)
{
case 1:
[task setCompleted: [NSCalendarDate calendarDate]];
break;
case 2:
[task setStatus: @"IN-PROCESS"];
break;
case 3:
[task setStatus: @"CANCELLED"];
break;
default:
[task setStatus: @"NEEDS-ACTION"];
}
content = [[task parent] versitString];
ex = [[self clientObject] saveContentString: content];
if (ex != nil) {
[self setErrorText:[ex reason]];
return self;
}
return [self redirectToLocation: [self completeURIForMethod: @".."]];
return todo;
}
- (id)acceptAction {
return [self acceptOrDeclineAction:YES];
}
- (id)declineAction {
return [self acceptOrDeclineAction:NO];
}
- (NSString *) saveUrl
- (NSString *) saveURL
{
return [NSString stringWithFormat: @"%@/saveAsTask",
[[self clientObject] baseURL]];
}
// TODO: add tentatively
- (NSString *) _toolbarForCalObject
{
SOGoUser *currentUser;
SOGoTaskObject *clientObject;
NSString *filename, *email;
iCalPerson *person;
iCalPersonPartStat participationStatus;
- (id)acceptOrDeclineAction:(BOOL)_accept {
// TODO: this should live in the SoObjects
NSException *ex;
clientObject = [self clientObject];
currentUser = [[self context] activeUser];
email = [currentUser email];
if ([clientObject isOrganizer: email
orOwner: [currentUser login]])
filename = @"SOGoTaskObject.toolbar";
else
{
if ([clientObject isParticipant: email])
{
person = [[clientObject component: NO] findParticipantWithEmail: email];
participationStatus = [person participationStatus];
if (participationStatus == iCalPersonPartStatAccepted)
filename = @"SOGoTaskObjectDecline.toolbar";
else if (participationStatus == iCalPersonPartStatDeclined)
filename = @"SOGoTaskObjectAccept.toolbar";
else
filename = @"SOGoTaskObjectAcceptOrDecline.toolbar";
}
else
filename = @"";
}
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 filename;
}
- (void) setHasStartDate: (BOOL) aBool
- (NSString *) toolbar
{
hasStartDate = aBool;
return ([self _toolbarForCalObject]);
}
/* icalendar values */
- (void) setTaskStartDate: (NSCalendarDate *) newTaskStartDate
{
ASSIGN (taskStartDate, newTaskStartDate);
}
- (NSCalendarDate *) taskStartDate
{
return taskStartDate;
}
- (void) setHasStartDate: (BOOL) newHasStartDate
{
hasStartDate = newHasStartDate;
}
- (BOOL) hasStartDate
{
return (!newTask && [self taskStartDate] != nil);
return hasStartDate;
}
- (BOOL) startDateDisabled
{
return (![self hasStartDate]);
return !hasStartDate;
}
- (void) setHasDueDate: (BOOL) aBool
- (void) setTaskDueDate: (NSCalendarDate *) newTaskDueDate
{
hasDueDate = aBool;
ASSIGN (taskDueDate, newTaskDueDate);
}
- (NSCalendarDate *) taskDueDate
{
return taskDueDate;
}
- (void) setHasDueDate: (BOOL) newHasDueDate
{
hasDueDate = newHasDueDate;
}
- (BOOL) hasDueDate
{
return (!newTask && [self taskDueDate] != nil);
return hasDueDate;
}
- (BOOL) dueDateDisabled
{
return (![self hasDueDate]);
return !hasDueDate;
}
- (void) setDueDateDisabled: (BOOL) aBool
- (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;
}
- (NSArray *) statusList
{
static NSArray *statusItems = nil;
if (!statusItems)
{
statusItems = [NSArray arrayWithObjects: @"NEEDS-ACTION",
@"IN-PROCESS",
@"COMPLETED",
@"CANCELLED",
nil];
[statusItems retain];
}
return statusItems;
}
- (NSString *) itemStatusText
{
return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
}
- (void) setItem: (NSString *) newItem
{
item = newItem;
}
- (NSString *) item
{
return item;
}
- (NSString *) repeat
{
return @"";
}
- (void) setRepeat: (NSString *) newRepeat
{
}
- (void) setStartDateDisabled: (BOOL) aBool
- (NSString *) status
{
return status;
}
@end /* UIxTaskEditor */
- (void) setStatus: (NSString *) newStatus
{
status = newStatus;
}
- (void) setStatusDate: (NSCalendarDate *) newStatusDate
{
ASSIGN (statusDate, newStatusDate);
}
- (NSCalendarDate *) statusDate
{
return statusDate;
}
- (BOOL) statusDateDisabled
{
return ![status isEqualToString: @"COMPLETED"];
}
- (BOOL) statusPercentDisabled
{
NSLog (@"status: '%@'", status);
return ([status length] == 0
|| [status isEqualToString: @"CANCELLED"]);
}
- (void) setStatusPercent: (NSString *) newStatusPercent
{
ASSIGN (statusPercent, newStatusPercent);
}
- (NSString *) statusPercent
{
return statusPercent;
}
/* 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, *dueDate;
NSString *duration;
unsigned int minutes;
todo = (iCalToDo *) [[self clientObject] component: NO];
if (todo)
{
startDate = [todo startDate];
dueDate = [todo due];
hasStartDate = (startDate != nil);
hasDueDate = (dueDate != nil);
ASSIGN (status, [todo status]);
if ([status isEqualToString: @"COMPLETED"])
ASSIGN (statusDate, [todo completed]);
else
ASSIGN (statusDate, [self newStartDate]);
ASSIGN (statusPercent, [todo percentComplete]);
}
else
{
startDate = [self newStartDate];
duration = [self queryParameterForKey:@"dur"];
if ([duration length] > 0)
minutes = [duration intValue];
else
minutes = 60;
dueDate = [startDate dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: minutes seconds: 0];
hasStartDate = NO;
hasDueDate = NO;
ASSIGN (statusDate, [self newStartDate]);
ASSIGN (status, @"");
ASSIGN (statusPercent, @"");
}
ASSIGN (taskStartDate, startDate);
ASSIGN (taskDueDate, dueDate);
/* 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/%@/editAsTask",
[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
{
SOGoTaskObject *clientObject;
NSString *iCalString;
clientObject = [self clientObject];
iCalString = [[clientObject calendar: NO] versitString];
[clientObject saveContentString: iCalString];
return [self jsCloseWithRefreshMethod: @"refreshTasks()"];
}
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
inContext: (WOContext*) context
{
return ([[self clientObject] isKindOfClass: [SOGoTaskObject class]]
&& [[request method] isEqualToString: @"POST"]);
}
- (void) takeValuesFromRequest: (WORequest *) _rq
inContext: (WOContext *) _ctx
{
SOGoTaskObject *clientObject;
clientObject = [self clientObject];
todo = (iCalToDo *) [clientObject component: YES];
[super takeValuesFromRequest: _rq inContext: _ctx];
if (hasStartDate)
[todo setStartDate: taskStartDate];
if (hasDueDate)
[todo setDue: taskDueDate];
if ([status isEqualToString: @"COMPLETED"])
[todo setCompleted: statusDate];
else
[todo setCompleted: nil];
if ([status length] > 0)
{
[todo setStatus: status];
[todo setPercentComplete: statusPercent];
}
else
{
[todo setStatus: @""];
[todo setPercentComplete: @""];
}
}
@end