diff --git a/ChangeLog b/ChangeLog index 0e391456e..be2d218da 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,48 @@ +2008-07-17 Wolfgang Sourdeau + + * UI/Scheduler/UIxCalListingActions.m ([UIxCalListingActions + -eventsBlocksAction]): event blocks now that the "c_recurrence_id" + virtual field into account. + + * UI/Common/UIxObjectActions.m ([UIxObjectActions -deleteAction]): + new action method, moved from UIxContactView so that it is + generalized to all SOGoObjects. + + * SoObjects/Appointments/SOGoCalendarComponent.m + ([SOGoCalendarComponent -init]): we now save the calendar to avoid + multiple parsing and instantiation of children components. + ([SOGoCalendarComponent -toOneRelationshipKeys]): declare + components as children. + ([SOGoCalendarComponent + -lookupName:lookupNameinContext:localContextacquire:acquire]): + handle the search for components and their occurences. + ([SOGoCalendarComponent -saveComponent:newObject]): added code to + update recurrence ids whenever the user saves a new version of the + master component. + + * SoObjects/Appointments/SOGoAppointmentObject.m ([SOGoAppointmentObject -occurence:occ]) + ([SOGoAppointmentObject -newOccurenceWithID:recID]) + ([SOGoAppointmentObject -prepareDeleteOccurence:occurence]): new + methods to handle occurences. + + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([SOGoAppointmentFolder -fixupCycleRecord:_recordcycleRange:_r]): + declare a new "c_recurrence_id" field, that will be overwritten + with further exception occurences. + + * UI/Scheduler/UIxOccurenceDialog.[hm]: new dialog asking whether + to edit/delete all occurences of a recurrent event or only the one + selected. + + * SoObjects/Appointments/SOGoComponentOccurence.[hm]: super class + of new classes belows. + + * SoObjects/Appointments/SOGoAppointmentOccurence.[hm]: new class + module that handle occurences within events. + + * SoObjects/Appointments/SOGoTaskOccurence.[hm]: new class + module that handle occurences within todos. + 2008-07-16 Wolfgang Sourdeau * UI/Scheduler/UIxCalListingActions.m ([UIxCalListingActions diff --git a/NEWS b/NEWS index e0eaf6ddd..3069c112b 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ address book in the Address Book module - fixed various bugs occuring with Firefox 3 - fixed bug where selecting the current day cell would not select the header day cell and vice-versa in the daily and weekly views - the events are now computed in the server code again, in order to speedup the drawing of events as well as to fix the bug where events would be shifted back or forth of one day, depending on how their start time would be compared to UTC time +- implemented the handling of exceptional occurences of recurrent events 0.9.0-20080520 (1.0 rc6) ------------------------ diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index 858b49b63..471e9a01b 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -20,6 +20,9 @@ Appointments_OBJC_FILES = \ SOGoCalendarComponent.m \ SOGoAppointmentObject.m \ SOGoTaskObject.m \ + SOGoComponentOccurence.m \ + SOGoAppointmentOccurence.m \ + SOGoTaskOccurence.m \ SOGoAppointmentFolder.m \ SOGoAppointmentFolders.m \ SOGoFreeBusyObject.m \ diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 980b43e2f..304b48f02 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -618,6 +618,7 @@ static Class sogoAppointmentFolderKlass = Nil; cycleRange: (NGCalendarDateRange *) _r { NSMutableDictionary *md; + NSNumber *dateSecs; id tmp; md = [[_record mutableCopy] autorelease]; @@ -627,7 +628,9 @@ static Class sogoAppointmentFolderKlass = Nil; tmp = [_r startDate]; [tmp setTimeZone: timeZone]; [md setObject:tmp forKey:@"startDate"]; - [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"]; + dateSecs = [NSNumber numberWithInt: [tmp timeIntervalSince1970]]; + [md setObject: dateSecs forKey: @"c_startdate"]; + [md setObject: dateSecs forKey: @"c_recurrence_id"]; tmp = [_r endDate]; [tmp setTimeZone: timeZone]; [md setObject:tmp forKey:@"endDate"]; @@ -682,11 +685,11 @@ static Class sogoAppointmentFolderKlass = Nil; { NSCalendarDate *startDate, *recurrenceId; NSMutableDictionary *newRecord; + NSDictionary *oldRecord; NGCalendarDateRange *newRecordRange; int recordIndex; newRecord = nil; - recurrenceId = [component recurrenceId]; if ([dateRange containsDate: recurrenceId]) { @@ -698,6 +701,11 @@ static Class sogoAppointmentFolderKlass = Nil; if ([dateRange containsDate: startDate]) { newRecord = [self fixupRecord: [component quickRecord]]; + [newRecord setObject: [NSNumber numberWithInt: 1] + forKey: @"c_iscycle"]; + oldRecord = [ma objectAtIndex: recordIndex]; + [newRecord setObject: [oldRecord objectForKey: @"c_recurrence_id"] + forKey: @"c_recurrence_id"]; [ma replaceObjectAtIndex: recordIndex withObject: newRecord]; } else @@ -754,6 +762,7 @@ static Class sogoAppointmentFolderKlass = Nil; intoArray: (NSMutableArray *) _ma { NSMutableDictionary *row, *fixedRow; + NSMutableArray *recordArray; NSDictionary *cycleinfo; NSCalendarDate *startDate, *endDate; NGCalendarDateRange *fir, *rRange; @@ -761,6 +770,8 @@ static Class sogoAppointmentFolderKlass = Nil; unsigned i, count; NSString *content; + recordArray = [NSMutableArray array]; + content = [_row objectForKey: @"c_cycleinfo"]; if (![content isNotNull]) { @@ -800,12 +811,14 @@ static Class sogoAppointmentFolderKlass = Nil; rRange = [ranges objectAtIndex: i]; fixedRow = [self fixupCycleRecord: row cycleRange: rRange]; if (fixedRow) - [_ma addObject:fixedRow]; + [recordArray addObject: fixedRow]; } [self _appendCycleExceptionsFromRow: row forRange: _r - toArray: _ma]; + toArray: recordArray]; + + [_ma addObjectsFromArray: recordArray]; } - (NSArray *) fixupCyclicRecords: (NSArray *) _records diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index ee022bb64..7854490e1 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -44,11 +44,12 @@ #import #import -#import "NSArray+Appointments.h" -#import "SOGoAppointmentFolder.h" #import "iCalEventChanges+SOGo.h" #import "iCalEntityObject+SOGo.h" #import "iCalPerson+SOGo.h" +#import "NSArray+Appointments.h" +#import "SOGoAppointmentFolder.h" +#import "SOGoAppointmentOccurence.h" #import "SOGoAppointmentObject.h" @@ -129,6 +130,35 @@ return @"vevent"; } +- (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) occ +{ + return [SOGoAppointmentOccurence occurenceWithComponent: occ + withMasterComponent: [self component: NO + secure: NO] + inContainer: self]; +} + +- (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) recID +{ + iCalEvent *newOccurence; + NSCalendarDate *date; + unsigned int interval; + + newOccurence = (iCalEvent *) [super newOccurenceWithID: recID]; + date = [newOccurence recurrenceId]; + interval = [[newOccurence endDate] + timeIntervalSinceDate: [newOccurence startDate]]; + [newOccurence setStartDate: date]; + [newOccurence setEndDate: [date addYear: 0 + month: 0 + day: 0 + hour: 0 + minute: 0 + second: interval]]; + + return newOccurence; +} + - (SOGoAppointmentObject *) _lookupEvent: (NSString *) eventUID forUID: (NSString *) uid { @@ -195,6 +225,7 @@ } } +#warning what about occurences? - (void) _handleRemovedUsers: (NSArray *) attendees { NSEnumerator *enumerator; @@ -573,7 +604,7 @@ return ex; } -- (void) prepareDelete +- (void) prepareDeleteOccurence: (iCalEvent *) occurence { iCalEvent *event; SOGoUser *currentUser; @@ -583,23 +614,32 @@ { currentUser = [context activeUser]; event = [self component: NO secure: NO]; + if (!occurence) + occurence = event; if ([event userIsOrganizer: currentUser]) { - attendees = [event attendeesWithoutUser: currentUser]; + attendees = [occurence attendeesWithoutUser: currentUser]; + if (![attendees count] && event != occurence) + attendees = [event attendeesWithoutUser: currentUser]; if ([attendees count]) { [self _handleRemovedUsers: attendees]; [self sendEMailUsingTemplateNamed: @"Deletion" forOldObject: nil - andNewObject: [event itipEntryWithMethod: @"cancel"] + andNewObject: [occurence itipEntryWithMethod: @"cancel"] toAttendees: attendees]; } } - else if ([event userIsParticipant: currentUser]) + else if ([occurence userIsParticipant: currentUser]) [self changeParticipationStatus: @"DECLINED"]; } } +- (void) prepareDelete +{ + [self prepareDeleteOccurence: nil]; +} + /* message type */ - (NSString *) outlookMessageClass diff --git a/SoObjects/Appointments/SOGoAppointmentOccurence.h b/SoObjects/Appointments/SOGoAppointmentOccurence.h new file mode 100644 index 000000000..c037b1774 --- /dev/null +++ b/SoObjects/Appointments/SOGoAppointmentOccurence.h @@ -0,0 +1,32 @@ +/* SOGoAppointmentOccurence.h - this file is part of SOGo + * + * Copyright (C) 2008 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. + */ + +#ifndef SOGOAPPOINTMENTOCCURENCE_H +#define SOGOAPPOINTMENTOCCURENCE_H + +#import "SOGoComponentOccurence.h" + +@interface SOGoAppointmentOccurence : SOGoComponentOccurence + +@end + +#endif /* SOGOAPPOINTMENTOCCURENCE_H */ diff --git a/SoObjects/Appointments/SOGoAppointmentOccurence.m b/SoObjects/Appointments/SOGoAppointmentOccurence.m new file mode 100644 index 000000000..f543226a5 --- /dev/null +++ b/SoObjects/Appointments/SOGoAppointmentOccurence.m @@ -0,0 +1,27 @@ +/* SOGoAppointmentOccurence.m - this file is part of SOGo + * + * Copyright (C) 2008 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. + */ + +#import "SOGoAppointmentOccurence.h" + +@implementation SOGoAppointmentOccurence + +@end diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index e259c7f68..a941cc0a8 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -25,6 +25,8 @@ #import +#import "SOGoComponentOccurence.h" + @class NSArray; @class NSString; @@ -33,8 +35,14 @@ @class iCalRepeatableEntityObject; @class SOGoUser; +@class SOGoComponentOccurence; -@interface SOGoCalendarComponent : SOGoContentObject +@interface SOGoCalendarComponent : SOGoContentObject +{ + iCalCalendar *fullCalendar; + iCalCalendar *safeCalendar; + iCalCalendar *originalCalendar; +} - (NSString *) componentTag; @@ -66,6 +74,11 @@ - (iCalPerson *) iCalPersonWithUID: (NSString *) uid; - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons; +/* recurrences */ +- (SOGoComponentOccurence *) + occurence: (iCalRepeatableEntityObject *) component; +- (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) recID; + @end #endif /* SOGOCALENDARCOMPONENT_H */ diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 09f339cd5..7b3567084 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -53,7 +53,9 @@ #import "SOGoAptMailNotification.h" #import "iCalEntityObject+SOGo.h" #import "iCalPerson+SOGo.h" +#import "iCalRepeatableEntityObject+SOGo.h" #import "SOGoCalendarComponent.h" +#import "SOGoComponentOccurence.h" static BOOL sendEMailNotifications = NO; @@ -74,6 +76,26 @@ static BOOL sendEMailNotifications = NO; } } +- (id) init +{ + if ((self = [super init])) + { + fullCalendar = nil; + safeCalendar = nil; + originalCalendar = nil; + } + + return self; +} + +- (void) dealloc +{ + [fullCalendar release]; + [safeCalendar release]; + [originalCalendar release]; + [super dealloc]; +} + - (NSString *) davContentType { return @"text/calendar"; @@ -146,6 +168,150 @@ static BOOL sendEMailNotifications = NO; return iCalString; } +static inline BOOL +_occurenceHasID (iCalRepeatableEntityObject *occurence, NSString *recID) +{ + unsigned int seconds, recSeconds; + + seconds = [recID intValue]; + recSeconds = [[occurence recurrenceId] timeIntervalSince1970]; + + return (seconds == recSeconds); +} + +- (iCalRepeatableEntityObject *) lookupOccurence: (NSString *) recID +{ + iCalRepeatableEntityObject *component, *occurence, *currentOccurence; + NSArray *occurences; + unsigned int count, max; + + occurence = nil; + + component = [self component: NO secure: NO]; + if ([component hasRecurrenceRules]) + { + occurences = [[self calendar: NO secure: NO] allObjects]; + max = [occurences count]; + count = 1; + while (!occurence && count < max) + { + currentOccurence = [occurences objectAtIndex: count]; + if (_occurenceHasID (currentOccurence, recID)) + occurence = currentOccurence; + else + count++; + } + } + + return occurence; +} + +- (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) component +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + +- (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) recID +{ + iCalRepeatableEntityObject *masterOccurence, *newOccurence; + iCalCalendar *calendar; + NSCalendarDate *recDate; + + recDate = [NSCalendarDate dateWithTimeIntervalSince1970: [recID intValue]]; + masterOccurence = [self component: NO secure: NO]; + if ([masterOccurence doesOccurOnDate: recDate]) + { + newOccurence = [masterOccurence mutableCopy]; + [newOccurence autorelease]; + [newOccurence removeAllRecurrenceRules]; + [newOccurence removeAllExceptionRules]; + [newOccurence removeAllExceptionDates]; + [newOccurence setOrganizer: nil]; + [newOccurence setRecurrenceId: recDate]; + + calendar = [newOccurence parent]; + [calendar addChild: newOccurence]; + } + else + newOccurence = nil; + + return newOccurence; +} + +- (BOOL) isFolderish +{ + return YES; +} + +- (id) toManyRelationshipKeys +{ + return nil; +} + +- (id) toOneRelationshipKeys +{ + NSMutableArray *keys; + NSArray *occurences; + NSCalendarDate *recID; + unsigned int count, max, seconds; + + keys = [NSMutableArray array]; + [keys addObject: @"master"]; + occurences = [[self calendar: NO secure: NO] allObjects]; + max = [occurences count]; + for (count = 1; count < max; count++) + { + recID = [[occurences objectAtIndex: count] recurrenceId]; + if (recID) + { + seconds = [recID timeIntervalSince1970]; + [keys addObject: [NSString stringWithFormat: @"occurence%d", + seconds]]; + } + } + + return keys; +} + +- (id) lookupName: (NSString *) lookupName + inContext: (id) localContext + acquire: (BOOL) acquire +{ + id obj; + iCalRepeatableEntityObject *occurence; + NSString *recID; + BOOL isNewOccurence; + + obj = [super lookupName: lookupName + inContext: localContext + acquire: acquire]; + if (!obj) + { + if ([lookupName isEqualToString: @"master"]) + obj = [self occurence: [self component: NO secure: NO]]; + else if ([lookupName hasPrefix: @"occurence"]) + { + recID = [lookupName substringFromIndex: 9]; + occurence = [self lookupOccurence: recID]; + if (!occurence) + { + occurence = [self newOccurenceWithID: recID]; + isNewOccurence = YES; + } + if (occurence) + { + obj = [self occurence: occurence]; + if (isNewOccurence) + [obj setIsNew: isNewOccurence]; + } + } + } + + return obj; +} + - (NSString *) contentAsString { NSString *secureContent; @@ -166,34 +332,45 @@ static BOOL sendEMailNotifications = NO; - (iCalCalendar *) calendar: (BOOL) create secure: (BOOL) secure { NSString *componentTag; - CardGroup *newComponent; - iCalCalendar *calendar; + iCalRepeatableEntityObject *newComponent; + iCalCalendar **calendar; NSString *iCalString; if (secure) - iCalString = [self secureContentAsString]; + calendar = &safeCalendar; else - iCalString = content; + calendar = &fullCalendar; - if ([iCalString length] > 0) - calendar = [iCalCalendar parseSingleFromSource: iCalString]; - else + if (!*calendar) { - if (create) + if (secure) + iCalString = [self secureContentAsString]; + else + iCalString = content; + + if ([iCalString length] > 0) { - calendar = [iCalCalendar groupWithTag: @"vcalendar"]; - [calendar setVersion: @"2.0"]; - [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"]; - componentTag = [[self componentTag] uppercaseString]; - newComponent = [[calendar classForTag: componentTag] - groupWithTag: componentTag]; - [calendar addChild: newComponent]; + ASSIGN (*calendar, [iCalCalendar parseSingleFromSource: iCalString]); + if (!secure) + originalCalendar = [*calendar copy]; } else - calendar = nil; + { + if (create) + { + ASSIGN (*calendar, [iCalCalendar groupWithTag: @"vcalendar"]); + [*calendar setVersion: @"2.0"]; + [*calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"]; + componentTag = [[self componentTag] uppercaseString]; + newComponent = [[*calendar classForTag: componentTag] + groupWithTag: componentTag]; + [newComponent setUid: [self globallyUniqueObjectId]]; + [*calendar addChild: newComponent]; + } + } } - return calendar; + return *calendar; } - (id) component: (BOOL) create secure: (BOOL) secure @@ -202,10 +379,36 @@ static BOOL sendEMailNotifications = NO; firstChildWithTag: [self componentTag]]; } +- (void) _updateRecurrenceIDs +{ + iCalRepeatableEntityObject *master, *oldMaster, *currentComponent; + int deltaSecs; + NSArray *components; + unsigned int count, max; + NSCalendarDate *recID; + + master = [self component: NO secure: NO]; + oldMaster = (iCalRepeatableEntityObject *) + [originalCalendar firstChildWithTag: [self componentTag]]; + deltaSecs = [[master startDate] + timeIntervalSinceDate: [oldMaster startDate]]; + components = [fullCalendar allObjects]; + max = [components count]; + for (count = 1; count < max; count++) + { + currentComponent = [components objectAtIndex: count]; + recID = [[currentComponent recurrenceId] addTimeInterval: deltaSecs]; + [currentComponent setRecurrenceId: recID]; + } +} + - (void) saveComponent: (iCalRepeatableEntityObject *) newObject { NSString *newiCalString; + if (!isNew + && [newObject isRecurrent]) + [self _updateRecurrenceIDs]; newiCalString = [[newObject parent] versitString]; [self saveContentString: newiCalString]; @@ -633,4 +836,11 @@ static BOOL sendEMailNotifications = NO; return roles; } +/* SOGoComponentOccurence protocol */ + +- (iCalRepeatableEntityObject *) occurence +{ + return [self component: YES secure: NO]; +} + @end diff --git a/SoObjects/Appointments/SOGoComponentOccurence.h b/SoObjects/Appointments/SOGoComponentOccurence.h new file mode 100644 index 000000000..07413b414 --- /dev/null +++ b/SoObjects/Appointments/SOGoComponentOccurence.h @@ -0,0 +1,56 @@ +/* SOGoComponentOccurence.h - this file is part of SOGo + * + * Copyright (C) 2008 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. + */ + +#ifndef SOGOCOMPONENTOCCURENCE_H +#define SOGOCOMPONENTOCCURENCE_H + +#import + +@class iCalRepeatableEntityObject; +@class SOGoCalendarComponent; + +@protocol SOGoComponentOccurence + +- (iCalRepeatableEntityObject *) occurence; +- (BOOL) isNew; +- (id) delete; + +@end + +@interface SOGoComponentOccurence : SOGoObject +{ + iCalRepeatableEntityObject *component; + iCalRepeatableEntityObject *master; + BOOL isNew; +} + ++ (id) occurenceWithComponent: (iCalRepeatableEntityObject *) newComponent + withMasterComponent: (iCalRepeatableEntityObject *) newMaster + inContainer: (SOGoCalendarComponent *) newContainer; + +- (void) setComponent: (iCalRepeatableEntityObject *) newComponent; +- (void) setMasterComponent: (iCalRepeatableEntityObject *) newMaster; +- (void) setIsNew: (BOOL) newIsNew; + +@end + +#endif /* SOGOCOMPONENTOCCURENCE_H */ diff --git a/SoObjects/Appointments/SOGoComponentOccurence.m b/SoObjects/Appointments/SOGoComponentOccurence.m new file mode 100644 index 000000000..4f4281d31 --- /dev/null +++ b/SoObjects/Appointments/SOGoComponentOccurence.m @@ -0,0 +1,163 @@ +/* SOGoComponentOccurence.m - this file is part of SOGo + * + * Copyright (C) 2008 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. + */ + +#import +#import + +#import +#import + +#import "SOGoComponentOccurence.h" + +#import "SOGoCalendarComponent.h" +@interface SOGoCalendarComponent (OccurenceExtensions) + +- (void) prepareDeleteOccurence: (iCalRepeatableEntityObject *) component; + +@end + +@implementation SOGoComponentOccurence + ++ (id) occurenceWithComponent: (iCalRepeatableEntityObject *) newComponent + withMasterComponent: (iCalRepeatableEntityObject *) newMaster + inContainer: (SOGoCalendarComponent *) newContainer +{ + SOGoComponentOccurence *occurence; + unsigned int seconds; + NSString *newName; + + if (newComponent == newMaster) + newName = @"master"; + else + { + seconds = [[newComponent recurrenceId] timeIntervalSince1970]; + newName = [NSString stringWithFormat: @"occurence%d", seconds]; + }; + occurence = [self objectWithName: newName inContainer: newContainer]; + [occurence setComponent: newComponent]; + [occurence setMasterComponent: newMaster]; + + return occurence; +} + +- (id) init +{ + if ((self = [super init])) + { + component = nil; + master = nil; + isNew = NO; + } + + return self; +} + +- (void) setIsNew: (BOOL) newIsNew +{ + isNew = newIsNew; +} + +- (BOOL) isNew +{ + return isNew; +} + +- (void) dealloc +{ + [component release]; + [master release]; + [super dealloc]; +} + +/* SOGoObject overrides */ + +- (BOOL) isFolderish +{ + return NO; +} + +- (NSString *) contentAsString +{ + return [component versitString]; +} + +- (NSString *) davContentLength +{ + unsigned int length; + + length = [[self contentAsString] + lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + + return [NSString stringWithFormat: @"%u", length]; +} + +/* /SOGoObject overrides */ + +- (void) setComponent: (iCalRepeatableEntityObject *) newComponent +{ + ASSIGN (component, newComponent); +} + +- (void) setMasterComponent: (iCalRepeatableEntityObject *) newMaster +{ + ASSIGN (master, newMaster); +} + +- (NSArray *) aclsForUser: (NSString *) uid +{ + return [container aclsForUser: uid]; +} + +/* SOGoComponentOccurence protocol */ + +- (iCalRepeatableEntityObject *) occurence +{ + return component; +} + +- (id) delete +{ + NSException *error; + iCalCalendar *parent; + + if (component == master) + error = [container delete]; + else + { + if ([container respondsToSelector: @selector (prepareDeleteOccurence:)]) + [container prepareDeleteOccurence: component]; + [master addToExceptionDates: [component startDate]]; + parent = [component parent]; + [[parent children] removeObject: component]; + [container saveComponent: master]; + error = nil; + } + + return error; +} + +- (void) saveComponent: (id) newEvent +{ + [container saveComponent: newEvent]; +} + +@end diff --git a/SoObjects/Appointments/SOGoTaskObject.m b/SoObjects/Appointments/SOGoTaskObject.m index 16bbb6254..d1e20139a 100644 --- a/SoObjects/Appointments/SOGoTaskObject.m +++ b/SoObjects/Appointments/SOGoTaskObject.m @@ -35,6 +35,7 @@ #import "NSArray+Appointments.h" #import "SOGoAptMailNotification.h" #import "SOGoAppointmentFolder.h" +#import "SOGoTaskOccurence.h" #import "SOGoTaskObject.h" @@ -45,6 +46,14 @@ return @"vtodo"; } +- (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) occ +{ + return [SOGoTaskOccurence occurenceWithComponent: occ + withMasterComponent: [self component: NO + secure: NO] + inContainer: self]; +} + /* message type */ - (NSString *) outlookMessageClass diff --git a/SoObjects/Appointments/SOGoTaskOccurence.h b/SoObjects/Appointments/SOGoTaskOccurence.h new file mode 100644 index 000000000..1ad101d48 --- /dev/null +++ b/SoObjects/Appointments/SOGoTaskOccurence.h @@ -0,0 +1,32 @@ +/* SOGoTaskOccurence.h - this file is part of SOGo + * + * Copyright (C) 2008 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. + */ + +#ifndef SOGOTASKOCCURENCE_H +#define SOGOTASKOCCURENCE_H + +#import "SOGoComponentOccurence.h" + +@interface SOGoTaskOccurence : SOGoComponentOccurence + +@end + +#endif /* SOGOTASKOCCURENCE_H */ diff --git a/SoObjects/Appointments/SOGoTaskOccurence.m b/SoObjects/Appointments/SOGoTaskOccurence.m new file mode 100644 index 000000000..af5d406a3 --- /dev/null +++ b/SoObjects/Appointments/SOGoTaskOccurence.m @@ -0,0 +1,27 @@ +/* SOGoTaskOccurence.m - this file is part of SOGo + * + * Copyright (C) 2008 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. + */ + +#import "SOGoTaskOccurence.h" + +@implementation SOGoTaskOccurence + +@end diff --git a/SoObjects/Appointments/iCalEvent+SOGo.m b/SoObjects/Appointments/iCalEvent+SOGo.m index a74dee55c..691ae23cb 100644 --- a/SoObjects/Appointments/iCalEvent+SOGo.m +++ b/SoObjects/Appointments/iCalEvent+SOGo.m @@ -25,6 +25,7 @@ #import #import +#import #import #import @@ -192,4 +193,15 @@ return row; } +- (NGCalendarDateRange *) firstOccurenceRange +{ + return [NGCalendarDateRange calendarDateRangeWithStartDate: [self startDate] + endDate: [self endDate]]; +} + +- (unsigned int) occurenceInterval +{ + return [[self endDate] timeIntervalSinceDate: [self startDate]]; +} + @end diff --git a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.h b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.h index 2a54131e1..3ab8ba2e0 100644 --- a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.h +++ b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.h @@ -29,5 +29,6 @@ @interface iCalRepeatableEntityObject (SOGoExtensions) - (NSString *) cycleInfo; +- (BOOL) doesOccurOnDate: (NSCalendarDate *) occurenceDate; @end diff --git a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m index edb0d2dd0..4e9504e3c 100644 --- a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m @@ -25,6 +25,8 @@ #import #import +#import +#import #import "iCalRepeatableEntityObject+SOGo.h" @@ -86,4 +88,42 @@ return value; } +- (NGCalendarDateRange *) firstOccurenceRange +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + +- (unsigned int) occurenceInterval +{ + [self subclassResponsibility: _cmd]; + + return 0; +} + +- (BOOL) doesOccurOnDate: (NSCalendarDate *) occurenceDate +{ + NSArray *ranges; + NGCalendarDateRange *checkRange; + NSCalendarDate *endDate; + BOOL doesOccur; + + doesOccur = [self isRecurrent]; + if (doesOccur) + { + endDate = [occurenceDate addTimeInterval: [self occurenceInterval]]; + checkRange = [NGCalendarDateRange calendarDateRangeWithStartDate: occurenceDate + endDate: endDate]; + ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: checkRange + firstInstanceCalendarDateRange: [self firstOccurenceRange] + recurrenceRules: [self recurrenceRules] + exceptionRules: [self exceptionRules] + exceptionDates: [self exceptionDates]]; + doesOccur = [ranges dateRangeArrayContainsDate: occurenceDate]; + } + + return doesOccur; +} + @end diff --git a/SoObjects/Appointments/iCalToDo+SOGo.m b/SoObjects/Appointments/iCalToDo+SOGo.m index 3c5e75131..81ac2ed6e 100644 --- a/SoObjects/Appointments/iCalToDo+SOGo.m +++ b/SoObjects/Appointments/iCalToDo+SOGo.m @@ -26,6 +26,7 @@ #import #import +#import #import #import @@ -169,4 +170,15 @@ return row; } +- (NGCalendarDateRange *) firstOccurenceRange +{ + return [NGCalendarDateRange calendarDateRangeWithStartDate: [self startDate] + endDate: [self due]]; +} + +- (unsigned int) occurenceInterval +{ + return [[self due] timeIntervalSinceDate: [self startDate]]; +} + @end diff --git a/SoObjects/Appointments/product.plist b/SoObjects/Appointments/product.plist index d8a5652ac..e9081775e 100644 --- a/SoObjects/Appointments/product.plist +++ b/SoObjects/Appointments/product.plist @@ -52,6 +52,26 @@ SOGoTaskObject = { superclass = "SOGoCalendarComponent"; }; + SOGoComponentOccurence = { + superclass = "SOGoObject"; + protectedBy = "Access Object"; + defaultRoles = { + "ViewAllComponent" = ( "Owner", "Organizer", "Participant", "ComponentModifier", "ComponentResponder", "ComponentViewer" ); + "ViewDAndT" = ( "Organizer", "Participant", "ComponentDAndTViewer" ); + "ModifyComponent" = ( "Owner", "Organizer", "ComponentModifier" ); + "RespondToComponent" = ( "Participant", "ComponentModifier", "ComponentResponder" ); + "Access Object" = ( "Owner", "Organizer", "Participant", "ComponentModifier", "ComponentResponder", "ComponentViewer", "ComponentDAndTViewer" ); + "Change Images and Files" = ( "Owner", "Organizer", "ComponentModifier" ); + "Access Contents Information" = ( "Owner", "Organizer", "Participant", "ComponentModifier", "ComponentResponder", "ComponentViewer", "ComponentDAndTViewer" ); + "WebDAV Access" = ( "Owner", "Organizer", "Participant", "ComponentModifier", "ComponentResponder", "ComponentViewer", "ComponentDAndTViewer" ); + }; + }; + SOGoAppointmentOccurence = { + superclass = "SOGoComponentOccurence"; + }; + SOGoTaskOccurence = { + superclass = "SOGoComponentOccurence"; + }; SOGoFreeBusyObject = { superclass = "SOGoContentObject"; protectedBy = "Access Contents Information"; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index f09f18cf8..7cfe4aa9e 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -692,8 +692,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; NSArray *sogoRoles; NSString *rqMethod; - rolesForObject = [NSMutableArray new]; - [rolesForObject autorelease]; + rolesForObject = [NSMutableArray array]; sogoRoles = [super rolesForObject: object inContext: context]; if (sogoRoles) diff --git a/UI/Common/UIxObjectActions.m b/UI/Common/UIxObjectActions.m index dec91048a..c51dbbc36 100644 --- a/UI/Common/UIxObjectActions.m +++ b/UI/Common/UIxObjectActions.m @@ -31,6 +31,8 @@ #import #import +#import "WODirectAction+SOGo.h" + #import "UIxObjectActions.h" @implementation UIxObjectActions @@ -71,4 +73,15 @@ return response; } +- (WOResponse *) deleteAction +{ + WOResponse *response; + + response = (WOResponse *) [[self clientObject] delete]; + if (!response) + response = [self responseWithStatus: 204]; + + return response; +} + @end diff --git a/UI/Common/product.plist b/UI/Common/product.plist index 5c4c53cad..f807824de 100644 --- a/UI/Common/product.plist +++ b/UI/Common/product.plist @@ -40,6 +40,11 @@ actionClass = "UIxObjectActions"; actionName = "removeUserFromAcls"; }; + delete = { + protectedBy = "Delete Objects"; + actionClass = "UIxObjectActions"; + actionName = "delete"; + }; acls = { protectedBy = "ReadAcls"; pageName = "UIxAclEditor"; diff --git a/UI/Contacts/UIxContactView.h b/UI/Contacts/UIxContactView.h index 3560553e6..749caa57e 100644 --- a/UI/Contacts/UIxContactView.h +++ b/UI/Contacts/UIxContactView.h @@ -34,8 +34,6 @@ CardElement *workAdr; } -- (BOOL)isDeletableClientObject; - @end #endif /* UIXCONTACTVIEW_H */ diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index cc6f525f3..42cfc9649 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -516,32 +516,4 @@ return self; } -- (BOOL) isDeletableClientObject -{ - return [[self clientObject] respondsToSelector: @selector(delete)]; -} - -- (id) deleteAction -{ - NSException *ex; - - if (![self isDeletableClientObject]) - /* return 400 == Bad Request */ - return [NSException exceptionWithHTTPStatus: 400 - reason:@"method cannot be invoked on " - @"the specified object"]; - - ex = [[self clientObject] delete]; - if (ex) - { - // TODO: improve error handling - [self debugWithFormat: @"failed to delete: %@", ex]; - - return ex; - } - - return [self responseWithStatus: 200 - andString: [[self clientObject] nameInContainer]]; -} - @end /* UIxContactView */ diff --git a/UI/MailerUI/UIxMailView.m b/UI/MailerUI/UIxMailView.m index 56c88a9d6..acd1a0b2e 100644 --- a/UI/MailerUI/UIxMailView.m +++ b/UI/MailerUI/UIxMailView.m @@ -41,35 +41,26 @@ id currentAddress; } -- (BOOL)isDeletableClientObject; - @end @implementation UIxMailView static NSString *mailETag = nil; -+ (int)version { - return [super version] + 0 /* v2 */; -} - -+ (void)initialize { ++ (void) initialize +{ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - NSAssert2([super version] == 2, - @"invalid superclass (%@) version %i !", - NSStringFromClass([self superclass]), [super version]); - - if ([ud boolForKey:@"SOGoDontUseETagsForMailViewer"]) { + if ([ud boolForKey:@"SOGoDontUseETagsForMailViewer"]) NSLog(@"Note: usage of constant etag for mailer viewer is disabled."); - } - else { - mailETag = [[NSString alloc] initWithFormat:@"\"imap4url_%d_%d_%03d\"", - UIX_MAILER_MAJOR_VERSION, - UIX_MAILER_MINOR_VERSION, - UIX_MAILER_SUBMINOR_VERSION]; - NSLog(@"Note: using constant etag for mail viewer: '%@'", mailETag); - } + else + { + mailETag = [[NSString alloc] initWithFormat:@"\"imap4url_%d_%d_%03d\"", + UIX_MAILER_MAJOR_VERSION, + UIX_MAILER_MINOR_VERSION, + UIX_MAILER_SUBMINOR_VERSION]; + NSLog(@"Note: using constant etag for mail viewer: '%@'", mailETag); + } } - (void)dealloc { @@ -193,11 +184,6 @@ static NSString *mailETag = nil; return self; } -- (BOOL) isDeletableClientObject -{ - return [[self clientObject] respondsToSelector: @selector (delete)]; -} - - (BOOL) isInlineViewer { return NO; diff --git a/UI/MainUI/product.plist b/UI/MainUI/product.plist index ce67497dd..c93fbf0cb 100644 --- a/UI/MainUI/product.plist +++ b/UI/MainUI/product.plist @@ -33,7 +33,8 @@ }; SOGoContentObject = { superclass = "SOGoObject"; - defaultAccess = "Access Contents Information"; + defaultAccess = "allow"; + /* defaultAccess = "Access Contents Information"; */ protectedBy = "Access Object"; defaultRoles = { "Access Contents Information" = ( "Owner", "ObjectViewer", "ObjectEditor" ); diff --git a/UI/SOGoUI/UIxComponent.h b/UI/SOGoUI/UIxComponent.h index e70d4af0b..056cdaa17 100644 --- a/UI/SOGoUI/UIxComponent.h +++ b/UI/SOGoUI/UIxComponent.h @@ -107,7 +107,7 @@ - (WOResponse *) redirectToLocation: (NSString *) newLocation; /* Debugging */ -- (BOOL)isUIxDebugEnabled; +- (BOOL) isUIxDebugEnabled; @end diff --git a/UI/Scheduler/Dutch.lproj/Localizable.strings b/UI/Scheduler/Dutch.lproj/Localizable.strings index 014e68f7a..4a4217c6a 100644 --- a/UI/Scheduler/Dutch.lproj/Localizable.strings +++ b/UI/Scheduler/Dutch.lproj/Localizable.strings @@ -456,6 +456,10 @@ vtodo_class2 = "(Vertrouwelijke taak)"; "Please select an event or a task." = "Selecteer een afspraak of een taak."; +"editRepeatingItem" = "Het item dat u bewerkt is een herhalend item. Wilt u alle items of enkel dit item bewerken?"; +"button_thisOccurrenceOnly" = "Enkel dit item"; +"button_allOccurrences" = "Alle herhalingen"; + /* Properties dialog */ "Name:" = "Naam:"; "Color:" = "Kleur:"; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 74c07732a..f3c471b35 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -473,6 +473,10 @@ vtodo_class2 = "(Confidential task)"; "Please select an event or a task." = "Please select an event or a task."; +"editRepeatingItem" = "The item you are editing is a repeating item. Do you want to edit all occurences of it or only this single instance?"; +"button_thisOccurrenceOnly" = "This occurence only"; +"button_allOccurrences" = "All occurences"; + /* Properties dialog */ "Name:" = "Name:"; "Color:" = "Color:"; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index d429e86a0..21637a405 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -470,6 +470,10 @@ vtodo_class2 = "(Tâche confidentielle)"; "Please select an event or a task." = "Veuillez sélectionner un événement ou une tâche."; +"editRepeatingItem" = "L'élément que vous éditez est récurrent. Voulez-vous modifier toutes ses occurrences ou seulement celle-ci ?"; +"button_thisOccurrenceOnly" = "Cette occurence seulement"; +"button_allOccurrences" = "Toutes les occurences"; + /* Properties dialog */ "Name:" = "Nom :"; "Color:" = "Couleur :"; diff --git a/UI/Scheduler/GNUmakefile b/UI/Scheduler/GNUmakefile index 796a7e016..26e7ccfcc 100644 --- a/UI/Scheduler/GNUmakefile +++ b/UI/Scheduler/GNUmakefile @@ -43,7 +43,8 @@ SchedulerUI_OBJC_FILES = \ UIxCalParticipationStatusView.m \ UIxCalMonthOverview.m \ UIxCalMonthViewOld.m \ - UIxRecurrenceEditor.m + UIxRecurrenceEditor.m \ + UIxOccurenceDialog.m # UIxAppointmentProposal.m # UIxTaskProposal.m diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index b03f4a6da..59affd44a 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -457,6 +457,10 @@ vtodo_class2 = "(Confidential task)"; "Please select an event or a task." = "Bitte wählen Sie einen Termin oder eine Aufgabe aus!"; +"editRepeatingItem" = "Der Eintrag, den Sie bearbeiten, ist ein wiederkehrender Eintrag. Wollen Sie alle seine Ereignisse bearbeiten oder nur diese einzelne Instanz?"; +"button_thisOccurrenceOnly" = "Nur dieses Ereignis"; +"button_allOccurrences" = "Alle Ereignisse"; + /* Properties dialog */ "Name:" = "Name:"; "Color:" = "Farbe:"; diff --git a/UI/Scheduler/Italian.lproj/Localizable.strings b/UI/Scheduler/Italian.lproj/Localizable.strings index 83b6707c7..2049b9a39 100644 --- a/UI/Scheduler/Italian.lproj/Localizable.strings +++ b/UI/Scheduler/Italian.lproj/Localizable.strings @@ -471,6 +471,10 @@ vtodo_class2 = "(Attività confidenziale)"; "From" = "Da"; "To" = "A"; +"editRepeatingItem" = "L'elemento che si sta modificando è un elemento ripetuto. Si vogliono modificare tutte le sue occorrenze o solo questa?"; +"button_thisOccurrenceOnly" = "Solamente questa occorrenza"; +"button_allOccurrences" = "Tutte le occorrenze"; + /* Properties dialog */ "Name:" = "Nome:"; "Color:" = "Colore:"; diff --git a/UI/Scheduler/Spanish.lproj/Localizable.strings b/UI/Scheduler/Spanish.lproj/Localizable.strings index a6b3960d9..5f30853cd 100644 --- a/UI/Scheduler/Spanish.lproj/Localizable.strings +++ b/UI/Scheduler/Spanish.lproj/Localizable.strings @@ -477,6 +477,10 @@ vtodo_class2 = "(Tarea confidencial)"; "Please select an event or a task." = "Seleccione un evento o tarea, por favor"; +"editRepeatingItem" = "El elemento que está editando es un elemento repetitivo. ¿Quiere editar todas sus ocurrencias o sólo la que ha señalado?"; +"button_thisOccurrenceOnly" = "Sólo esta ocurrencia"; +"button_allOccurrences" = "Todas las ocurrencias"; + /* Properties dialog */ "Name:" = "Nombre:"; "Color:" = "Color:"; diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index 81cff9b69..ec2dd99ef 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -38,6 +38,8 @@ #import #import +#import + #import "UIxComponentEditor.h" #import "UIxAppointmentEditor.h" @@ -71,7 +73,7 @@ { if (!event) { - event = (iCalEvent *) [[self clientObject] component: YES secure: YES]; + event = (iCalEvent *) [[self clientObject] occurence]; [event retain]; } @@ -165,9 +167,12 @@ NSCalendarDate *startDate, *endDate; NSString *duration; unsigned int minutes; + SOGoObject *co; [self event]; - if ([[self clientObject] isNew]) + co = [self clientObject]; + if ([co isNew] + && [co isKindOfClass: [SOGoCalendarComponent class]]) { startDate = [self newStartDate]; duration = [self queryParameterForKey:@"dur"]; @@ -231,7 +236,7 @@ actionName = [[request requestHandlerPath] lastPathComponent]; - return ([[self clientObject] isKindOfClass: [SOGoAppointmentObject class]] + return ([[self clientObject] conformsToProtocol: @protocol (SOGoComponentOccurence)] && [actionName hasPrefix: @"save"]); } diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 8c270e5d4..5102a459a 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -65,7 +65,8 @@ static NSArray *tasksFields = nil; @"c_status", @"c_title", @"c_startdate", @"c_enddate", @"c_location", @"c_isallday", @"c_classification", @"c_partmails", - @"c_partstates", @"c_owner", @"c_iscycle", nil]; + @"c_partstates", @"c_owner", @"c_iscycle", + @"c_recurrence_id", nil]; [eventsFields retain]; } if (!tasksFields) @@ -404,7 +405,7 @@ _feedBlockWithMonthBasedData(NSMutableDictionary *block, unsigned int start, end: (unsigned int) end cname: (NSString *) cName onDay: (unsigned int) dayStart - recurrence: (BOOL) recurrence + recurrenceTime: (unsigned int) recurrenceTime userState: (iCalPersonPartStat) userState { NSMutableDictionary *block; @@ -416,8 +417,9 @@ _feedBlockWithMonthBasedData(NSMutableDictionary *block, unsigned int start, else _feedBlockWithMonthBasedData (block, start, userTimeZone, dateFormatter); [block setObject: cName forKey: @"cname"]; - if (recurrence) - [block setObject: @"1" forKey: @"recurrence"]; + if (recurrenceTime) + [block setObject: [NSNumber numberWithInt: recurrenceTime] + forKey: @"recurrenceTime"]; if (userState != iCalPersonPartStatOther) [block setObject: [NSNumber numberWithInt: userState] forKey: @"userState"]; @@ -463,11 +465,10 @@ _userStateInEvent (NSArray *event) withEvent: (NSArray *) event { unsigned int currentDayStart, startSecs, endsSecs, currentStart, eventStart, - eventEnd, offset; + eventEnd, offset, recurrenceTime; NSMutableArray *currentDay; NSMutableDictionary *eventBlock; NSString *eventCName; - BOOL recurrence; iCalPersonPartStat userState; startSecs = (unsigned int) [startDate timeIntervalSince1970]; @@ -475,7 +476,10 @@ _userStateInEvent (NSArray *event) eventStart = [[event objectAtIndex: 4] unsignedIntValue]; eventEnd = [[event objectAtIndex: 5] unsignedIntValue]; - recurrence = [[event objectAtIndex: 12] boolValue]; + if ([[event objectAtIndex: 12] boolValue]) + recurrenceTime = [[event objectAtIndex: 13] unsignedIntValue]; + else + recurrenceTime = 0; currentStart = eventStart; if (currentStart < startSecs) @@ -500,7 +504,7 @@ _userStateInEvent (NSArray *event) end: currentDayStart + dayLength - 1 cname: eventCName onDay: currentDayStart - recurrence: recurrence + recurrenceTime: recurrenceTime userState: userState]; [currentDay addObject: eventBlock]; currentDayStart += dayLength; @@ -512,7 +516,7 @@ _userStateInEvent (NSArray *event) end: eventEnd cname: eventCName onDay: currentDayStart - recurrence: recurrence + recurrenceTime: recurrenceTime userState: userState]; [currentDay addObject: eventBlock]; } diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index d9e136b1e..6d5c13c79 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -399,6 +399,11 @@ iRANGE(2); /* accessors */ +- (BOOL) isChildOccurence +{ + return [[self clientObject] isKindOfClass: [SOGoComponentOccurence class]]; +} + - (void) setItem: (id) _item { ASSIGN (item, _item); @@ -418,7 +423,7 @@ iRANGE(2); { NSString *tag; - tag = [[self clientObject] componentTag]; + tag = [[component tag] lowercaseString]; return [self labelForKey: [NSString stringWithFormat: @"%@_%@", item, tag]]; } @@ -456,7 +461,7 @@ iRANGE(2); - (BOOL) canBeOrganizer { NSString *owner; - SOGoCalendarComponent *co; + SOGoObject *co; SOGoUser *currentUser; BOOL hasOrganizer; SoSecurityManager *sm; @@ -801,7 +806,7 @@ iRANGE(2); SOGoAppointmentFolders *parentFolder; fDisplayName = [item displayName]; - folder = [[self clientObject] container]; + folder = [self componentCalendar]; parentFolder = [folder container]; if ([fDisplayName isEqualToString: [parentFolder defaultFolderName]]) fDisplayName = [self labelForKey: fDisplayName]; @@ -823,7 +828,9 @@ iRANGE(2); SOGoAppointmentFolder *calendar; calendar = [[self clientObject] container]; - + if ([calendar isKindOfClass: [SOGoCalendarComponent class]]) + calendar = [calendar container]; + return calendar; } @@ -1125,8 +1132,8 @@ RANGE(2); - (BOOL) isWriteableClientObject { - return [[self clientObject] - respondsToSelector: @selector(saveContentString:)]; + return [[self clientObject] + respondsToSelector: @selector(saveContentString:)]; } /* access */ @@ -1485,54 +1492,56 @@ RANGE(2); clientObject = [self clientObject]; if ([clientObject isNew]) { - [component setUid: [clientObject nameInContainer]]; [component setCreated: now]; [component setTimeStampAsDate: now]; } [component setPriority: priority]; [component setLastModified: now]; - // We remove any repeat rules - if (!repeat && [component hasRecurrenceRules]) - [component removeAllRecurrenceRules]; - else if ([repeat caseInsensitiveCompare: @"-"] != NSOrderedSame) + if (![self isChildOccurence]) { - rule = [iCalRecurrenceRule new]; + // We remove any repeat rules + if (!repeat && [component hasRecurrenceRules]) + [component removeAllRecurrenceRules]; + else if ([repeat caseInsensitiveCompare: @"-"] != NSOrderedSame) + { + rule = [iCalRecurrenceRule new]; - [rule setInterval: @"1"]; - - if ([repeat caseInsensitiveCompare: @"BI-WEEKLY"] == NSOrderedSame) - { - [rule setFrequency: iCalRecurrenceFrequenceWeekly]; - [rule setInterval: @"2"]; - } - else if ([repeat caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame) - { - [rule setByDayMask: (iCalWeekDayMonday - |iCalWeekDayTuesday - |iCalWeekDayWednesday - |iCalWeekDayThursday - |iCalWeekDayFriday)]; - [rule setFrequency: iCalRecurrenceFrequenceDaily]; - } - else if ([repeat caseInsensitiveCompare: @"MONTHLY"] == NSOrderedSame - || [repeat caseInsensitiveCompare: @"DAILY"] == NSOrderedSame - || [repeat caseInsensitiveCompare: @"WEEKLY"] == NSOrderedSame - || [repeat caseInsensitiveCompare: @"YEARLY"] == NSOrderedSame) - { [rule setInterval: @"1"]; - [rule setFrequency: - (iCalRecurrenceFrequency) [rule valueForFrequency: repeat]]; - } - else - { - // We have a CUSTOM recurrence. Let's decode what kind of custome recurrence - // we have and set that. - [self _handleCustomRRule: rule]; - } - [component setRecurrenceRules: [NSArray arrayWithObject: rule]]; - [rule release]; + if ([repeat caseInsensitiveCompare: @"BI-WEEKLY"] == NSOrderedSame) + { + [rule setFrequency: iCalRecurrenceFrequenceWeekly]; + [rule setInterval: @"2"]; + } + else if ([repeat caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame) + { + [rule setByDayMask: (iCalWeekDayMonday + |iCalWeekDayTuesday + |iCalWeekDayWednesday + |iCalWeekDayThursday + |iCalWeekDayFriday)]; + [rule setFrequency: iCalRecurrenceFrequenceDaily]; + } + else if ([repeat caseInsensitiveCompare: @"MONTHLY"] == NSOrderedSame + || [repeat caseInsensitiveCompare: @"DAILY"] == NSOrderedSame + || [repeat caseInsensitiveCompare: @"WEEKLY"] == NSOrderedSame + || [repeat caseInsensitiveCompare: @"YEARLY"] == NSOrderedSame) + { + [rule setInterval: @"1"]; + [rule setFrequency: + (iCalRecurrenceFrequency) [rule valueForFrequency: repeat]]; + } + else + { + // We have a CUSTOM recurrence. Let's decode what kind of custome recurrence + // we have and set that. + [self _handleCustomRRule: rule]; + } + + [component setRecurrenceRules: [NSArray arrayWithObject: rule]]; + [rule release]; + } } } diff --git a/UI/Scheduler/UIxOccurenceDialog.h b/UI/Scheduler/UIxOccurenceDialog.h new file mode 100644 index 000000000..bf945f116 --- /dev/null +++ b/UI/Scheduler/UIxOccurenceDialog.h @@ -0,0 +1,46 @@ +/* UIxOccurenceDialog.h - this file is part of SOGo + * + * Copyright (C) 2008 Inverse inc. + * + * 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. + */ + +#ifndef UIXRECURRENCEEDITOR_H +#define UIXRECURRENCEEDITOR_H + +#import + +@class NSString; + +@interface UIxOccurenceDialog : UIxComponent +{ + NSString *action; +} + +- (NSString *) action; + +- (NSString *) calendarFolder; +- (NSString *) componentName; +- (NSString *) recurrenceName; + +- (id ) defaultAction; +- (id ) confirmDeletionAction; + +@end + +#endif /* UIXRECURRENCEEDITOR_H */ diff --git a/UI/Scheduler/UIxOccurenceDialog.m b/UI/Scheduler/UIxOccurenceDialog.m new file mode 100644 index 000000000..c6b2e7a2d --- /dev/null +++ b/UI/Scheduler/UIxOccurenceDialog.m @@ -0,0 +1,90 @@ +/* UIxOccurenceDialog.m - this file is part of SOGo + * + * Copyright (C) 2008 Inverse inc. + * + * 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. + */ + +#import +#import + +#import + +#import + +#import "UIxOccurenceDialog.h" + +@implementation UIxOccurenceDialog + +- (id) init +{ + if ((self = [super init])) + action = nil; + + return self; +} + +- (void) dealloc +{ + [action release]; + [super dealloc]; +} + +- (NSString *) action +{ + return action; +} + +- (NSString *) calendarFolder +{ + SOGoCalendarComponent *component; + + component = [[self clientObject] container]; + + return [[component container] nameInContainer]; +} + +- (NSString *) componentName +{ + SOGoCalendarComponent *component; + + component = [[self clientObject] container]; + + return [component nameInContainer]; +} + +- (NSString *) recurrenceName +{ + return [[self clientObject] nameInContainer]; +} + +- (id ) defaultAction +{ + ASSIGN (action, @"edit"); + + return self; +} + +- (id ) confirmDeletionAction +{ + ASSIGN (action, @"delete"); + + return self; +} + +@end diff --git a/UI/Scheduler/UIxTaskEditor.m b/UI/Scheduler/UIxTaskEditor.m index 6b129716c..df27bf130 100644 --- a/UI/Scheduler/UIxTaskEditor.m +++ b/UI/Scheduler/UIxTaskEditor.m @@ -335,7 +335,7 @@ actionName = [[request requestHandlerPath] lastPathComponent]; - return ([[self clientObject] isKindOfClass: [SOGoTaskObject class]] + return ([[self clientObject] conformsToProtocol: @protocol (SOGoComponentOccurence)] && [actionName hasPrefix: @"save"]); } diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index b703b1489..490a2b64e 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -2,239 +2,293 @@ requires = ( MAIN, MainUI, CommonUI, Appointments, Contacts, ContactsUI ); publicResources = ( - previous_week.gif, - next_week.gif, - icon_view_overview.gif, - icon_view_overview_inactive.gif, - icon_view_chart.gif, - icon_view_chart_inactive.gif, - icon_view_list.gif, - icon_view_list_inactive.gif, - icon_view_columns.gif, - icon_view_columns_inactive.gif, - icon_popupcalendar.gif, - first.gif, - previous.gif, - next.gif, - last.gif, - skycalendar.html, - skycalendar.js, - green_corner.gif, - invisible_space_2.gif, - cycles.plist, - ); + previous_week.gif, + next_week.gif, + icon_view_overview.gif, + icon_view_overview_inactive.gif, + icon_view_chart.gif, + icon_view_chart_inactive.gif, + icon_view_list.gif, + icon_view_list_inactive.gif, + icon_view_columns.gif, + icon_view_columns_inactive.gif, + icon_popupcalendar.gif, + first.gif, + previous.gif, + next.gif, + last.gif, + skycalendar.html, + skycalendar.js, + green_corner.gif, + invisible_space_2.gif, + cycles.plist, + ); factories = { }; categories = { - SOGoAppointmentFolders = { - slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoAppointmentFolders.toolbar"; - }; + SOGoAppointmentFolders = { + slots = { + toolbar = { + protectedBy = "View"; + value = "SOGoAppointmentFolders.toolbar"; }; - methods = { - view = { - protectedBy = "View"; - pageName = "UIxCalMainView"; - }; - saveDragHandleState = { - protectedBy = "View"; - pageName = "UIxCalMainView"; - actionName = "saveDragHandleState"; - }; - dateselector = { - protectedBy = "View"; - pageName = "UIxCalDateSelector"; - }; - calendarslist = { - protectedBy = "View"; - pageName = "UIxCalendarSelector"; - actionName = "calendarsList"; - }; - eventslist = { - protectedBy = "View"; - actionClass = "UIxCalListingActions"; - actionName = "eventsList"; - }; - eventsblocks = { - protectedBy = "View"; - actionClass = "UIxCalListingActions"; - actionName = "eventsBlocks"; - }; - taskslist = { - protectedBy = "View"; - actionClass = "UIxCalListingActions"; - actionName = "tasksList"; - }; - dayview = { - protectedBy = "View"; - pageName = "UIxCalDayView"; - }; - multicolumndayview = { - protectedBy = "View"; - pageName = "UIxCalMulticolumnDayView"; - }; - weekview = { - protectedBy = "View"; - pageName = "UIxCalWeekView"; - }; - monthview = { - protectedBy = "View"; - pageName = "UIxCalMonthView"; - }; - show = { - protectedBy = "View"; - pageName = "UIxCalView"; - actionName = "redirectForUIDs"; - }; -// proposal = { -// protectedBy = "View"; -// pageName = "UIxAppointmentProposal"; -// }; -// proposalSearch = { -// protectedBy = "View"; -// pageName = "UIxAppointmentProposal"; -// actionName = "proposalSearch"; -// }; - userRights = { - protectedBy = "ReadAcls"; - pageName = "UIxCalUserRightsEditor"; - }; - saveUserRights = { - protectedBy = "Change Permissions"; - pageName = "UIxCalUserRightsEditor"; - actionName = "saveUserRights"; - }; - editAttendees = { - protectedBy = "View"; - pageName = "UIxAttendeesEditor"; - }; - editRecurrence = { - protectedBy = "View"; - pageName = "UIxRecurrenceEditor"; - }; - colorPicker = { - protectedBy = "View"; - pageName = "UIxColorPicker"; - }; + }; + methods = { + view = { + protectedBy = "View"; + pageName = "UIxCalMainView"; }; - }; + saveDragHandleState = { + protectedBy = "View"; + pageName = "UIxCalMainView"; + actionName = "saveDragHandleState"; + }; + dateselector = { + protectedBy = "View"; + pageName = "UIxCalDateSelector"; + }; + calendarslist = { + protectedBy = "View"; + pageName = "UIxCalendarSelector"; + actionName = "calendarsList"; + }; + eventslist = { + protectedBy = "View"; + actionClass = "UIxCalListingActions"; + actionName = "eventsList"; + }; + eventsblocks = { + protectedBy = "View"; + actionClass = "UIxCalListingActions"; + actionName = "eventsBlocks"; + }; + taskslist = { + protectedBy = "View"; + actionClass = "UIxCalListingActions"; + actionName = "tasksList"; + }; + dayview = { + protectedBy = "View"; + pageName = "UIxCalDayView"; + }; + multicolumndayview = { + protectedBy = "View"; + pageName = "UIxCalMulticolumnDayView"; + }; + weekview = { + protectedBy = "View"; + pageName = "UIxCalWeekView"; + }; + monthview = { + protectedBy = "View"; + pageName = "UIxCalMonthView"; + }; + show = { + protectedBy = "View"; + pageName = "UIxCalView"; + actionName = "redirectForUIDs"; + }; + // proposal = { + // protectedBy = "View"; + // pageName = "UIxAppointmentProposal"; + // }; + // proposalSearch = { + // protectedBy = "View"; + // pageName = "UIxAppointmentProposal"; + // actionName = "proposalSearch"; + // }; + userRights = { + protectedBy = "ReadAcls"; + pageName = "UIxCalUserRightsEditor"; + }; + saveUserRights = { + protectedBy = "Change Permissions"; + pageName = "UIxCalUserRightsEditor"; + actionName = "saveUserRights"; + }; + editAttendees = { + protectedBy = "View"; + pageName = "UIxAttendeesEditor"; + }; + editRecurrence = { + protectedBy = "View"; + pageName = "UIxRecurrenceEditor"; + }; + colorPicker = { + protectedBy = "View"; + pageName = "UIxColorPicker"; + }; + }; + }; - SOGoAppointmentFolder = { - methods = { - properties = { - protectedBy = "Access Contents Information"; - pageName = "UIxCalendarProperties"; - }; - saveProperties = { - protectedBy = "Access Contents Information"; - pageName = "UIxCalendarProperties"; - actionName = "saveProperties"; - }; - show = { - protectedBy = "View"; - pageName = "UIxCalView"; - actionName = "redirectForUIDs"; - }; - userRights = { - protectedBy = "ReadAcls"; - pageName = "UIxCalUserRightsEditor"; - }; - saveUserRights = { - protectedBy = "Change Permissions"; - pageName = "UIxCalUserRightsEditor"; - actionName = "saveUserRights"; - }; - newevent = { - protectedBy = "Add Documents, Images, and Files"; - pageName = "UIxAppointmentEditor"; - actionName = "new"; - }; - newtask = { - protectedBy = "Add Documents, Images, and Files"; - pageName = "UIxTaskEditor"; - actionName = "new"; - }; + SOGoAppointmentFolder = { + methods = { + properties = { + protectedBy = "Access Contents Information"; + pageName = "UIxCalendarProperties"; }; - }; + saveProperties = { + protectedBy = "Access Contents Information"; + pageName = "UIxCalendarProperties"; + actionName = "saveProperties"; + }; + show = { + protectedBy = "View"; + pageName = "UIxCalView"; + actionName = "redirectForUIDs"; + }; + userRights = { + protectedBy = "ReadAcls"; + pageName = "UIxCalUserRightsEditor"; + }; + saveUserRights = { + protectedBy = "Change Permissions"; + pageName = "UIxCalUserRightsEditor"; + actionName = "saveUserRights"; + }; + newevent = { + protectedBy = "Add Documents, Images, and Files"; + pageName = "UIxAppointmentEditor"; + actionName = "new"; + }; + newtask = { + protectedBy = "Add Documents, Images, and Files"; + pageName = "UIxTaskEditor"; + actionName = "new"; + }; + }; + }; - SOGoCalendarComponent = { - }; + SOGoCalendarComponent = { + }; - SOGoAppointmentObject = { - slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoAppointmentObject.toolbar"; - }; + SOGoAppointmentObject = { + slots = { + toolbar = { + protectedBy = "View"; + value = "SOGoAppointmentObject.toolbar"; }; - methods = { - edit = { - protectedBy = "ViewAllComponent"; - pageName = "UIxAppointmentEditor"; - }; - editAsAppointment = { - protectedBy = "ViewAllComponent"; - pageName = "UIxAppointmentEditor"; - }; - save = { - protectedBy = "ModifyComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "save"; - }; - saveAsAppointment = { - protectedBy = "ModifyComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "save"; - }; - accept = { - protectedBy = "RespondToComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "accept"; - }; - decline = { - protectedBy = "RespondToComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "decline"; - }; + }; + methods = { + edit = { + protectedBy = "ViewAllComponent"; + pageName = "UIxAppointmentEditor"; }; - }; + editAsAppointment = { + protectedBy = "ViewAllComponent"; + pageName = "UIxAppointmentEditor"; + }; + save = { + protectedBy = "ModifyComponent"; + pageName = "UIxAppointmentEditor"; + actionName = "save"; + }; + saveAsAppointment = { + protectedBy = "ModifyComponent"; + pageName = "UIxAppointmentEditor"; + actionName = "save"; + }; + accept = { + protectedBy = "RespondToComponent"; + pageName = "UIxAppointmentEditor"; + actionName = "accept"; + }; + decline = { + protectedBy = "RespondToComponent"; + pageName = "UIxAppointmentEditor"; + actionName = "decline"; + }; + }; + }; - SOGoTaskObject = { - slots = { - toolbar = { - protectedBy = "View"; - value = "SOGoAppointmentObject.toolbar"; - }; + SOGoTaskObject = { + slots = { + toolbar = { + protectedBy = "View"; + value = "SOGoAppointmentObject.toolbar"; }; - methods = { - edit = { - protectedBy = "ViewAllComponent"; - pageName = "UIxTaskEditor"; - }; - editAsTask = { - protectedBy = "ViewAllComponent"; - pageName = "UIxTaskEditor"; - }; - save = { - protectedBy = "ModifyComponent"; - pageName = "UIxTaskEditor"; - actionName = "save"; - }; - saveAsTask = { - protectedBy = "ModifyComponent"; - pageName = "UIxTaskEditor"; - actionName = "save"; - }; - changeStatus = { - protectedBy = "ModifyComponent"; - pageName = "UIxTaskEditor"; - actionName = "changeStatus"; - }; + }; + methods = { + edit = { + protectedBy = "ViewAllComponent"; + pageName = "UIxTaskEditor"; }; - }; + editAsTask = { + protectedBy = "ViewAllComponent"; + pageName = "UIxTaskEditor"; + }; + save = { + protectedBy = "ModifyComponent"; + pageName = "UIxTaskEditor"; + actionName = "save"; + }; + saveAsTask = { + protectedBy = "ModifyComponent"; + pageName = "UIxTaskEditor"; + actionName = "save"; + }; + changeStatus = { + protectedBy = "ModifyComponent"; + pageName = "UIxTaskEditor"; + actionName = "changeStatus"; + }; + }; + }; + + SOGoComponentOccurence = { + methods = { + confirmEditing = { + protectedBy = "ModifyComponent"; + pageName = "UIxOccurenceDialog"; + }; + confirmDeletion = { + protectedBy = "ModifyComponent"; + pageName = "UIxOccurenceDialog"; + actionName = "confirmDeletion"; + }; + }; + }; + + SOGoAppointmentOccurence = { + slots = { + toolbar = { + protectedBy = "View"; + value = "SOGoAppointmentObject.toolbar"; + }; + }; + methods = { + edit = { + protectedBy = "ViewAllComponent"; + pageName = "UIxAppointmentEditor"; + }; + save = { + protectedBy = "ModifyComponent"; + pageName = "UIxAppointmentEditor"; + actionName = "save"; + }; + }; + }; + + SOGoTaskOccurence = { + slots = { + toolbar = { + protectedBy = "View"; + value = "SOGoTaskObject.toolbar"; + }; + }; + methods = { + edit = { + protectedBy = "ViewAllComponent"; + pageName = "UIxTaskEditor"; + }; + save = { + protectedBy = "ModifyComponent"; + pageName = "UIxTaskEditor"; + actionName = "save"; + }; + }; + }; }; } diff --git a/UI/Templates/SchedulerUI/UIxComponentEditor.wox b/UI/Templates/SchedulerUI/UIxComponentEditor.wox index 965690323..d391bddd9 100644 --- a/UI/Templates/SchedulerUI/UIxComponentEditor.wox +++ b/UI/Templates/SchedulerUI/UIxComponentEditor.wox @@ -46,6 +46,7 @@ string="itemCategoryText" selection="category" /> + + + +
+ +
+
+
+ +
+
+ + +
+
+
diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 1f7de053f..b2b7bdb12 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -71,9 +71,14 @@ function onMenuNewTaskClick(event) { newEvent(this, "task"); } -function _editEventId(id, calendar) { - var urlstr = ApplicationBaseURL + calendar + "/" + id + "/edit"; +function _editEventId(id, calendar, recurrence) { var targetname = "SOGo_edit_" + id; + var urlstr = ApplicationBaseURL + calendar + "/" + id; + if (recurrence) { + urlstr += "/" + recurrence; + targetname += recurrence; + } + urlstr += "/edit"; var win = window.open(urlstr, "_blank", "width=490,height=470,resizable=0"); if (win) @@ -115,50 +120,60 @@ function _batchDeleteEvents() { function deleteEvent() { if (listOfSelection) { var nodes = listOfSelection.getSelectedRows(); - if (nodes.length > 0) { var label = ""; if (listOfSelection == $("tasksList")) label = labels["taskDeleteConfirmation"]; else label = labels["eventDeleteConfirmation"]; - - if (confirm(label)) { - if (document.deleteEventAjaxRequest) { + + if (nodes.length == 1 + && nodes[0].recurrenceTime) { + _editRecurrenceDialog(nodes[0], "confirmDeletion"); + } + else { + if (confirm(label)) { + if (document.deleteEventAjaxRequest) { document.deleteEventAjaxRequest.aborted = true; document.deleteEventAjaxRequest.abort(); - } - var sortedNodes = []; - var calendars = []; - - for (var i = 0; i < nodes.length; i++) { - var calendar = nodes[i].calendar; - if (!sortedNodes[calendar]) { - sortedNodes[calendar] = []; - calendars.push(calendar); - } - sortedNodes[calendar].push(nodes[i].cname); - } - for (var i = 0; i < calendars.length; i++) { - calendarsOfEventsToDelete.push(calendars[i]); - eventsToDelete.push(sortedNodes[calendars[i]]); - } - _batchDeleteEvents(); + } + var sortedNodes = []; + var calendars = []; + + for (var i = 0; i < nodes.length; i++) { + var calendar = nodes[i].calendar; + if (!sortedNodes[calendar]) { + sortedNodes[calendar] = []; + calendars.push(calendar); + } + sortedNodes[calendar].push(nodes[i].cname); + } + for (var i = 0; i < calendars.length; i++) { + calendarsOfEventsToDelete.push(calendars[i]); + eventsToDelete.push(sortedNodes[calendars[i]]); + } + _batchDeleteEvents(); + } } } else { window.alert(labels["Please select an event or a task."]); } } else if (selectedCalendarCell) { - var label = labels["eventDeleteConfirmation"]; - if (confirm(label)) { - if (document.deleteEventAjaxRequest) { - document.deleteEventAjaxRequest.aborted = true; - document.deleteEventAjaxRequest.abort(); + if (selectedCalendarCell[0].recurrenceTime) { + _editRecurrenceDialog(selectedCalendarCell[0], "confirmDeletion"); + } + else { + var label = labels["eventDeleteConfirmation"]; + if (confirm(label)) { + if (document.deleteEventAjaxRequest) { + document.deleteEventAjaxRequest.aborted = true; + document.deleteEventAjaxRequest.abort(); + } + eventsToDelete.push([selectedCalendarCell[0].cname]); + calendarsOfEventsToDelete.push(selectedCalendarCell[0].calendar); + _batchDeleteEvents(); } - eventsToDelete.push([selectedCalendarCell[0].cname]); - calendarsOfEventsToDelete.push(selectedCalendarCell[0].calendar); - _batchDeleteEvents(); } } else @@ -268,13 +283,97 @@ function deleteEventsFromViews(events) { } } +function _editRecurrenceDialog(eventDiv, method) { + var targetname = "SOGo_edit_" + eventDiv.cname + eventDiv.recurrenceTime; + var urlstr = (ApplicationBaseURL + eventDiv.calendar + "/" + eventDiv.cname + + "/occurence" + eventDiv.recurrenceTime + "/" + method); + var win = window.open(urlstr, "_blank", + "width=490,height=70,resizable=0"); + if (win) + win.focus(); +} + function editDoubleClickedEvent(event) { - _editEventId(this.cname, this.calendar); + if (this.recurrenceTime) + _editRecurrenceDialog(this, "confirmEditing"); + else + _editEventId(this.cname, this.calendar); preventDefault(event); event.cancelBubble = true; } +function performEventEdition(folder, event, recurrence) { + _editEventId(event, folder, recurrence); +} + +function performEventDeletion(folder, event, recurrence) { + if (calendarEvents) { + var eventEntry = calendarEvents[event]; + if (eventEntry) { + var urlstr = ApplicationBaseURL + folder + "/" + event; + var nodes; + if (recurrence) { + urlstr += "/" + recurrence; + var occurenceTime = recurrence.substring(9); + nodes = []; + for (var i = 0; i < eventEntry.siblings.length; i++) { + if (eventEntry.siblings[i].recurrenceTime + && eventEntry.siblings[i].recurrenceTime == occurenceTime) + nodes.push(eventEntry.siblings[i]); + } + } + else + nodes = eventEntry.siblings; + urlstr += "/delete"; + document.deleteEventAjaxRequest = triggerAjaxRequest(urlstr, + performDeleteEventCallback, + { nodes: nodes, + recurrence: recurrence }); + } + } +} + +function performDeleteEventCallback(http) { + if (http.readyState == 4) { + if (isHttpStatus204(http.status)) { + var nodes = http.callbackData.nodes; + var recurrenceTime = 0; + if (http.callbackData.recurrence) + recurrenceTime = http.callbackData.recurrence.substring(9); + var cName = nodes[0].cname; + var eventEntry = calendarEvents[cName]; + var node = nodes.pop(); + while (node) { + node.parentNode.removeChild(node); + node = nodes.pop(); + } + if (recurrenceTime) { + var row = $(cName + "-" + recurrenceTime); + if (row) + row.parentNode.removeChild(row); + } + else { + delete calendarEvents[cName]; + var tables = [ "eventsList", "tasksList" ]; + for (var i = 0; i < 2; i++) { + var table = $(tables[i]); + if (table.tBodies) + rows = table.tBodies[0].rows; + else + rows = $(table).childNodesWithTag("li"); + for (var j = rows.length; j > 0; j--) { + var row = $(rows[j - 1]); + var id = row.getAttribute("id"); + if (id.indexOf(cName) == 0) + row.parentNode.removeChild(row); + } + } + } + } + } +} + function onSelectAll() { var list = $("eventsList"); list.selectRowsMatchingClass("eventRow"); @@ -370,10 +469,15 @@ function eventsListCallback(http) { var row = document.createElement("tr"); table.tBodies[0].appendChild(row); $(row).addClassName("eventRow"); - row.setAttribute("id", escape(data[i][0])); + var rTime = data[i][13]; + var id = escape(data[i][0]); + if (rTime) + id += "-" + escape(rTime); + row.setAttribute("id", id); row.cname = escape(data[i][0]); row.calendar = data[i][1]; - + if (rTime) + row.recurrenceTime = escape(rTime); var startDate = new Date(); startDate.setTime(data[i][4] * 1000); row.day = startDate.getDayString(); @@ -391,12 +495,12 @@ function eventsListCallback(http) { td = $(document.createElement("td")); row.appendChild(td); td.observe("mousedown", listRowMouseDownHandler, true); - td.appendChild(document.createTextNode(data[i][13])); + td.appendChild(document.createTextNode(data[i][14])); td = $(document.createElement("td")); row.appendChild(td); td.observe("mousedown", listRowMouseDownHandler, true); - td.appendChild(document.createTextNode(data[i][14])); + td.appendChild(document.createTextNode(data[i][15])); td = $(document.createElement("td")); row.appendChild(td); @@ -761,16 +865,17 @@ function _drawCalendarAllDaysEvents(events) { } } -function newAllDayEventDIV(eventRep) { +function newBaseEventDIV(eventRep, event, eventText) { // cname, calendar, starts, lasts, // startHour, endHour, title) { var eventDiv = $(document.createElement("div")); - var event = calendarEvents[eventRep.cname]; if (!event.siblings) event.siblings = []; eventDiv.event = event; eventDiv.cname = event[0]; eventDiv.calendar = event[1]; + if (eventRep.recurrenceTime) + eventDiv.recurrenceTime = eventRep.recurrenceTime; eventDiv.addClassName("event"); if (eventRep.userState && userStates[eventRep.userState]) @@ -787,69 +892,6 @@ function newAllDayEventDIV(eventRep) { innerDiv.addClassName("eventInside"); innerDiv.addClassName("calendarFolder" + event[1]); - var gradientDiv = $(document.createElement("div")); - innerDiv.appendChild(gradientDiv); - gradientDiv.addClassName("gradient"); - var gradientImg = document.createElement("img"); - gradientDiv.appendChild(gradientImg); - gradientImg.src = ResourcesURL + "/event-gradient.png"; - - var textDiv = $(document.createElement("div")); - innerDiv.appendChild(textDiv); - textDiv.addClassName("text"); - textDiv.appendChild(document.createTextNode(event[3])); - - eventDiv.observe("mousedown", listRowMouseDownHandler); - eventDiv.observe("click", onCalendarSelectEvent); - eventDiv.observe("dblclick", editDoubleClickedEvent); - - event.siblings.push(eventDiv); - - return eventDiv; -} - -function _drawCalendarEvents(events) { - var daysView = $("daysView"); - var subdivs = daysView.childNodesWithTag("div"); - var days = subdivs[1].childNodesWithTag("div"); - for (var i = 0; i < events.length; i++) { - var parentDiv = days[i].childNodesWithTag("div")[0]; - for (var j = 0; j < events[i].length; j++) { - var eventRep = events[i][j]; - var eventDiv = newEventDIV(eventRep); - parentDiv.appendChild(eventDiv); - } - } -} - -function newEventDIV(eventRep) { -// cname, calendar, starts, lasts, -// startHour, endHour, title) { - var eventDiv = $(document.createElement("div")); - var event = calendarEvents[eventRep.cname]; - if (!event.siblings) - event.siblings = []; - eventDiv.event = event; - eventDiv.cname = event[0]; - eventDiv.calendar = event[1]; - - eventDiv.addClassName("event"); - if (eventRep.userState && userStates[eventRep.userState]) - eventDiv.addClassName(userStates[eventRep.userState]); - - eventDiv.addClassName("starts" + eventRep.start); - eventDiv.addClassName("lasts" + eventRep.length); - for (var i = 1; i < 5; i++) { - var shadowDiv = $(document.createElement("div")); - eventDiv.appendChild(shadowDiv); - shadowDiv.addClassName("shadow"); - shadowDiv.addClassName("shadow" + i); - } - var innerDiv = $(document.createElement("div")); - eventDiv.appendChild(innerDiv); - innerDiv.addClassName("eventInside"); - innerDiv.addClassName("calendarFolder" + event[1]); - var gradientDiv = $(document.createElement("div")); innerDiv.appendChild(gradientDiv); gradientDiv.addClassName("gradient"); @@ -860,20 +902,7 @@ function newEventDIV(eventRep) { var textDiv = $(document.createElement("div")); innerDiv.appendChild(textDiv); textDiv.addClassName("text"); -// if (startHour) { -// var headerSpan = document.createElement("span"); -// textDiv.appendChild(headerSpan); -// $(headerSpan).addClassName("eventHeader"); -// headerSpan.appendChild(document.createTextNode(startHour + " - " -// + endHour)); -// textDiv.appendChild(document.createElement("br")); -// } - textDiv.appendChild(document.createTextNode(event[3])); - - var pc = 100 / eventRep.siblings; - eventDiv.style.width = pc + "%"; - var left = eventRep.position * pc; - eventDiv.style.left = left + "%"; + textDiv.appendChild(document.createTextNode(eventText)); eventDiv.observe("mousedown", listRowMouseDownHandler); eventDiv.observe("click", onCalendarSelectEvent); @@ -884,6 +913,43 @@ function newEventDIV(eventRep) { return eventDiv; } +function newAllDayEventDIV(eventRep) { +// cname, calendar, starts, lasts, +// startHour, endHour, title) { + var event = calendarEvents[eventRep.cname]; + var eventDiv = newBaseEventDIV(eventRep, event, event[3]); + + return eventDiv; +} + +function _drawCalendarEvents(events) { + var daysView = $("daysView"); + var subdivs = daysView.childNodesWithTag("div"); + var days = subdivs[1].childNodesWithTag("div"); + for (var i = 0; i < events.length; i++) { + var parentDiv = days[i].childNodesWithTag("div")[0]; + for (var j = 0; j < events[i].length; j++) { + var eventRep = events[i][j]; + var eventDiv = newEventDIV(eventRep); + parentDiv.appendChild(eventDiv); + } + } +} + +function newEventDIV(eventRep) { + var event = calendarEvents[eventRep.cname]; + var eventDiv = newBaseEventDIV(eventRep, event, event[3]); + + var pc = 100 / eventRep.siblings; + eventDiv.style.width = pc + "%"; + var left = eventRep.position * pc; + eventDiv.style.left = left + "%"; + eventDiv.addClassName("starts" + eventRep.start); + eventDiv.addClassName("lasts" + eventRep.length); + + return eventDiv; +} + function _drawMonthCalendarEvents(events) { var daysView = $("monthDaysView"); var days = daysView.childNodesWithTag("div"); @@ -898,56 +964,14 @@ function _drawMonthCalendarEvents(events) { } function newMonthEventDIV(eventRep) { -// cname, calendar, starts, lasts, -// startHour, endHour, title) { - var eventDiv = $(document.createElement("div")); var event = calendarEvents[eventRep.cname]; - if (!event.siblings) - event.siblings = []; - eventDiv.event = event; - eventDiv.cname = event[0]; - eventDiv.calendar = event[1]; - - eventDiv.addClassName("event"); - if (eventRep.userState && userStates[eventRep.userState]) { - eventDiv.addClassName(userStates[eventRep.userState]); - log (eventDiv.getAttribute("class")); - } - - for (var i = 1; i < 5; i++) { - var shadowDiv = $(document.createElement("div")); - eventDiv.appendChild(shadowDiv); - shadowDiv.addClassName("shadow"); - shadowDiv.addClassName("shadow" + i); - } - var innerDiv = $(document.createElement("div")); - eventDiv.appendChild(innerDiv); - innerDiv.addClassName("eventInside"); - innerDiv.addClassName("calendarFolder" + event[1]); - - var gradientDiv = $(document.createElement("div")); - innerDiv.appendChild(gradientDiv); - gradientDiv.addClassName("gradient"); - var gradientImg = document.createElement("img"); - gradientDiv.appendChild(gradientImg); - gradientImg.src = ResourcesURL + "/event-gradient.png"; - - var textDiv = $(document.createElement("div")); - innerDiv.appendChild(textDiv); - textDiv.addClassName("textw"); - var eventText; if (event[7]) eventText = event[3]; else eventText = eventRep.starthour + " - " + event[3]; - textDiv.appendChild(document.createTextNode(eventText)); - eventDiv.observe("mousedown", listRowMouseDownHandler); - eventDiv.observe("click", onCalendarSelectEvent); - eventDiv.observe("dblclick", editDoubleClickedEvent); - - event.siblings.push(eventDiv); + var eventDiv = newBaseEventDIV(eventRep, event, eventText); return eventDiv; } diff --git a/UI/WebServerResources/UIxOccurenceDialog.css b/UI/WebServerResources/UIxOccurenceDialog.css new file mode 100644 index 000000000..2a5a4b658 --- /dev/null +++ b/UI/WebServerResources/UIxOccurenceDialog.css @@ -0,0 +1,13 @@ +DIV#message, DIV#windowButtons +{ margin: 10px; } + +DIV#windowButtons INPUT +{ text-align: center; } + +DIV#leftButtons +{ float: left; + width: 200px; + text-align: left; } + +DIV#rightButtons +{ text-align: right; } diff --git a/UI/WebServerResources/UIxOccurenceDialog.js b/UI/WebServerResources/UIxOccurenceDialog.js new file mode 100644 index 000000000..ade34bcce --- /dev/null +++ b/UI/WebServerResources/UIxOccurenceDialog.js @@ -0,0 +1,40 @@ +function onCancelButtonClick(event) { + window.close(); +} + +function onThisButtonClick(event) { + if (action == 'edit') + window.opener.performEventEdition(calendarFolder, componentName, + recurrenceName); + else if (action == 'delete') + window.opener.performEventDeletion(calendarFolder, componentName, + recurrenceName); + else + window.alert("Invalid action: " + action); + + window.close(); +} + +function onAllButtonClick(event) { + if (action == 'edit') + window.opener.performEventEdition(calendarFolder, componentName); + else if (action == 'delete') + window.opener.performEventDeletion(calendarFolder, componentName); + else + window.alert("Invalid action: " + action); + + window.close(); +} + +function onOccurenceDialogLoad() { + var thisButton = $("thisButton"); + thisButton.observe("click", onThisButtonClick); + + var allButton = $("allButton"); + allButton.observe("click", onAllButtonClick); + + var cancelButton = $("cancelButton"); + cancelButton.observe("click", onCancelButtonClick); +} + +FastInit.addOnLoad(onOccurenceDialogLoad);