diff --git a/ChangeLog b/ChangeLog index 5a498ba39..fba167e2e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,14 @@ 2007-03-18 Wolfgang Sourdeau + * 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. diff --git a/UI/Scheduler/UIxAppointmentEditor.h b/UI/Scheduler/UIxAppointmentEditor.h index 612de9ead..7254f5d67 100644 --- a/UI/Scheduler/UIxAppointmentEditor.h +++ b/UI/Scheduler/UIxAppointmentEditor.h @@ -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 * @@ -23,50 +23,38 @@ #ifndef UIXAPPOINTMENTEDITOR_H #define UIXAPPOINTMENTEDITOR_H -#import +#import +@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 ) defaultAction; -- (id ) saveAction; -- (id) acceptAction; -- (id) declineAction; - -- (NSString *) saveUrl; - -// TODO: add tentatively - -- (id) acceptOrDeclineAction: (BOOL) _accept; +- (NSString *) reminder; +- (void) setReminder: (NSString *) newReminder; @end diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index da2fda638..47e6801d1 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -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 + * + * 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 +#import +#import +#import - 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 +#import - 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 -#import - -#import -#import - -#import "common.h" -#import -#import -#import -#import -#import -#import "UIxComponent+Agenor.h" +#import +#import +#import +#import +#import +#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) 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 ) saveAction -{ - iCalEvent *apt; - iCalPerson *p; - id 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 ) 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 ) newAction +{ + NSString *objectId, *method, *uri; + id 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 ) 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 diff --git a/UI/Scheduler/UIxComponentEditor.h b/UI/Scheduler/UIxComponentEditor.h index b240e9e9c..e36e8035b 100644 --- a/UI/Scheduler/UIxComponentEditor.h +++ b/UI/Scheduler/UIxComponentEditor.h @@ -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 diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 41eb62bc2..ba876b507 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -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 diff --git a/UI/Scheduler/UIxTaskEditor.h b/UI/Scheduler/UIxTaskEditor.h index df43674ef..871593c41 100644 --- a/UI/Scheduler/UIxTaskEditor.h +++ b/UI/Scheduler/UIxTaskEditor.h @@ -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 * @@ -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 -@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 ) defaultAction; -- (id ) 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 */ diff --git a/UI/Scheduler/UIxTaskEditor.m b/UI/Scheduler/UIxTaskEditor.m index 8ff0ff6e1..b1a1478fe 100644 --- a/UI/Scheduler/UIxTaskEditor.m +++ b/UI/Scheduler/UIxTaskEditor.m @@ -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 + * + * 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 +#import +#import +#import - 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 +#import - 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 +#import +#import +#import +#import +#import +#import "UIxComponentEditor.h" #import "UIxTaskEditor.h" -/* TODO: CLEAN UP */ - -#import "common.h" -#import -#import -#import -#import -#import -#import -#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 ) 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 ) saveAction +- (void) dealloc { - iCalToDo *task; - iCalPerson *p; - id 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 ) 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 ) newAction +{ + NSString *objectId, *method, *uri; + id 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 ) 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