From 12856abe4d95398bb3b08bf42d62299f95c0a20e Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Mon, 25 Apr 2011 10:31:08 +0000 Subject: [PATCH] See ChangeLog Monotone-Parent: be9e28d5d42ed05605b27d2127cf29b07678495b Monotone-Revision: 5de6a9584cf27a2c1dad8d1ab8b84fc9ddab2720 Monotone-Author: ludovic@Sophos.ca Monotone-Date: 2011-04-25T10:31:08 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 7 + .../English.lproj/Localizable.strings | 3 + .../French.lproj/Localizable.strings | 3 + .../Appointments/SOGoAppointmentFolder.m | 12 +- .../Appointments/SOGoAppointmentObject.h | 27 +-- .../Appointments/SOGoAppointmentObject.m | 169 ++++++++++++++++-- .../Appointments/SOGoCalendarComponent.h | 2 +- .../Appointments/SOGoCalendarComponent.m | 4 +- SoObjects/Appointments/SOGoFreeBusyObject.h | 7 +- SoObjects/Appointments/SOGoFreeBusyObject.m | 7 +- SoObjects/SOGo/LDAPSource.h | 8 +- SoObjects/SOGo/LDAPSource.m | 64 ++++++- SoObjects/SOGo/SOGoGroup.m | 2 +- SoObjects/SOGo/SOGoUser.h | 11 +- SoObjects/SOGo/SOGoUser.m | 22 +++ SoObjects/SOGo/SOGoUserManager.m | 13 ++ UI/MailerUI/German.lproj/Localizable.strings | 4 +- UI/SOGoUI/UIxComponent.m | 7 +- UI/SOGoUI/UIxJSClose.h | 2 +- UI/SOGoUI/UIxJSClose.m | 2 +- UI/Scheduler/UIxAppointmentEditor.m | 19 +- .../SchedulerUI/UIxComponentEditor.wox | 2 +- UI/WebServerResources/UIxAppointmentEditor.js | 17 ++ 23 files changed, 340 insertions(+), 74 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9b1cc3ec5..b98904855 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2011-04-25 Ludovic Marcotte + + * Added the concept of "resources" in SOGo in order + to avoid double-bookings (if not more) and also, handle + auto-accepts. This works for the web interface and + over DAV - generating 403 errors in case of a conflict. + 2011-04-21 Ludovic Marcotte * Added the possibility of translating IMAP namespaces diff --git a/SoObjects/Appointments/English.lproj/Localizable.strings b/SoObjects/Appointments/English.lproj/Localizable.strings index 01a973e0c..1f432a7d9 100644 --- a/SoObjects/Appointments/English.lproj/Localizable.strings +++ b/SoObjects/Appointments/English.lproj/Localizable.strings @@ -60,3 +60,6 @@ vtodo_class2 = "(Confidential task)"; = "%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."; "%{Attendee} %{SentByText}has not yet decided upon your event invitation." = "%{Attendee} %{SentByText}has not yet decided upon your event invitation."; + +/* Resources */ +"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"."; \ No newline at end of file diff --git a/SoObjects/Appointments/French.lproj/Localizable.strings b/SoObjects/Appointments/French.lproj/Localizable.strings index bf6ef31f2..80be3f52d 100644 --- a/SoObjects/Appointments/French.lproj/Localizable.strings +++ b/SoObjects/Appointments/French.lproj/Localizable.strings @@ -60,3 +60,6 @@ vtodo_class2 = "(Tâche confidentielle)"; = "%{Attendee} %{SentByText}a délégué votre invitation à %{Delegate}."; "%{Attendee} %{SentByText}has not yet decided upon your event invitation." = "%{Attendee} %{SentByText}choisit de reporter sa décision par rapport à votre invitation."; + +/* Resources */ +"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Le nombre maximum (%{NumberOfSimultaneousBookings}) de réservation(s) simultanée(s) a été atteint pour la ressource \"%{Cn} %{SystemEmail}\"."; \ No newline at end of file diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 78117c7ac..55fedac19 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -2,14 +2,14 @@ Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG - This file is part of OpenGroupware.org. + This file is part of SOGo. - OGo is free software; you can redistribute it and/or modify it under + SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + SOGo 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. @@ -623,6 +623,9 @@ static NSNumber *sharedYes = nil; return record; } +// +// +// - (NSArray *) fixupRecords: (NSArray *) theRecords { // TODO: is the result supposed to be sorted by date? @@ -802,6 +805,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir [self _fixExceptionRecord: newRecord fromRow: row]; } +// +// +// - (void) _appendCycleExceptionsFromRow: (NSDictionary *) row firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir forRange: (NGCalendarDateRange *) dateRange diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index 9f13025e4..290f568b9 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -1,15 +1,14 @@ /* - Copyright (C) 2004-2005 SKYRIX Software AG - Copyright (C) 2007-2010 Inverse inc. + Copyright (C) 2007-2011 Inverse inc. - This file is part of OpenGroupware.org. + This file is part of SOGo - OGo is free software; you can redistribute it and/or modify it under + SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + SOGo 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. @@ -25,17 +24,6 @@ #import -/* - SOGoAppointmentObject - - Represents a single appointment. This SOPE controller object manages all the - attendee storages (that is, it might store into multiple folders for meeting - appointments!). - - Note: SOGoAppointmentObject do not need to exist yet. They can also be "new" - appointments with an externally generated unique key. -*/ - @class NSArray; @class NSException; @class NSString; @@ -61,13 +49,6 @@ - (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients from: (NSString *) originator; - (NSArray *) postCalDAVEventCancelTo: (NSArray *) recipients from: (NSString *) originator; -/* "iCal multifolder saves" */ - -// - (NSException *) saveContentString: (NSString *) _iCal -// baseSequence: (int) _v; -// - (NSException *) deleteWithBaseSequence: (int) _v; -// - (NSException *) saveContentString: (NSString *) _iCalString; - @end #endif /* __Appointments_SOGoAppointmentObject_H__ */ diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 29a308411..155ce6707 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -2,7 +2,7 @@ Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG - This file is part of OpenGroupware.org. + This file is part of SOGo SOGo 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 @@ -42,6 +42,7 @@ #import #import +#import #import #import #import @@ -198,7 +199,6 @@ // We check if the attendee that was added to a single occurence is // present in the master component. If not, we add it with a participation // status set to "DECLINED". - user = [SOGoUser userWithLogin: theUID]; person = [iCalPerson elementWithTag: @"attendee"]; [person setCn: [user cn]]; @@ -425,14 +425,117 @@ } // +// This methods scans the list of attendees. If they are +// considered as resource, it checks for conflicting +// dates for the event. +// +// We check for between startDate + 1 second and +// endDate - 1 second // // -- (void) _handleAddedUsers: (NSArray *) attendees - fromEvent: (iCalEvent *) newEvent +// It also CHANGES the participation status of resources +// depending on constraints defined on them. +// +// Note that it doesn't matter if it changes the participation +// status since in case of an error, nothing will get saved. +// +- (NSException *) _handleResourcesConflicts: (NSArray *) theAttendees + forEvent: (iCalEvent *) theEvent { - NSEnumerator *enumerator; iCalPerson *currentAttendee; + NSEnumerator *enumerator; NSString *currentUID; + SOGoUser *user; + + enumerator = [theAttendees objectEnumerator]; + + while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + + if (currentUID) + { + user = [SOGoUser userWithLogin: currentUID]; + + if ([user isResource]) + { + SOGoAppointmentFolder *folder; + NSCalendarDate *start, *end; + NSMutableArray *fbInfo; + int i; + + start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1]; + end = [[theEvent endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -1]; + + folder = [[SOGoUser userWithLogin: currentUID] + personalCalendarFolderInContext: context]; + + + fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start + to: end]]; + + // We first remove any occurences in the freebusy that corresponds to the + // current event. We do this to avoid raising a conflict if we move a 1 hour + // meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise. + for (i = [fbInfo count]-1; i >= 0; i--) + { + if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame) + [fbInfo removeObjectAtIndex: i]; + } + + if ([fbInfo count]) + { + // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit + // is imposed) or if numberOfSimultaneousBookings is greater than the number of + // overlapping events + if ([user numberOfSimultaneousBookings] == 0 || + [user numberOfSimultaneousBookings] > [fbInfo count]) + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + else + { + NSDictionary *values; + NSString *reason; + + values = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings", + [user cn], @"Cn", + [user systemEmail], @"SystemEmail", + nil]; + + reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"."]]; + + return [NSException exceptionWithHTTPStatus:403 + reason: reason]; + } + } + else + { + // No conflict, we auto-accept. We do this for resources automatically if no + // double-booking is observed. If it's not the desired behavior, just don't + // set the resource as one! + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + } + } + } + } + + return nil; +} + +// +// +// +- (NSException *) _handleAddedUsers: (NSArray *) attendees + fromEvent: (iCalEvent *) newEvent +{ + iCalPerson *currentAttendee; + NSEnumerator *enumerator; + NSString *currentUID; + NSException *e; + + // We check for conflicts + if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent])) + return e; enumerator = [attendees objectEnumerator]; while ((currentAttendee = [enumerator nextObject])) @@ -443,25 +546,31 @@ forUID: currentUID owner: owner]; } + + return nil; } // // // -- (void) _handleUpdatedEvent: (iCalEvent *) newEvent - fromOldEvent: (iCalEvent *) oldEvent +- (NSException *) _handleUpdatedEvent: (iCalEvent *) newEvent + fromOldEvent: (iCalEvent *) oldEvent { - NSArray *attendees; iCalEventChanges *changes; + NSArray *attendees; + NSException *ex; changes = [newEvent getChangesRelativeToEvent: oldEvent]; if ([changes sequenceShouldBeIncreased]) { // Set new attendees status to "needs action" and recompute changes when - // the list of attendees has changed. + // the list of attendees has changed. The list might have changed since + // by changing a major property of the event, we remove all the delegation + // chains to "other" attendees if ([self _requireResponseFromAttendees: newEvent]) changes = [newEvent getChangesRelativeToEvent: oldEvent]; } + attendees = [changes deletedAttendees]; if ([attendees count]) { @@ -475,6 +584,10 @@ forObject: newEvent to: attendees]; } + if ((ex = [self _handleResourcesConflicts: [newEvent attendees] + forEvent: newEvent])) + return ex; + attendees = [changes insertedAttendees]; if ([changes sequenceShouldBeIncreased]) { @@ -516,7 +629,9 @@ if ([attendees count]) { // Send an invitation to new attendees - [self _handleAddedUsers: attendees fromEvent: newEvent]; + if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent])) + return ex; + [self sendEMailUsingTemplateNamed: @"Invitation" forObject: [newEvent itipEntryWithMethod: @"request"] previousObject: oldEvent @@ -524,6 +639,8 @@ [self sendReceiptEmailUsingTemplateNamed: @"Invitation" forObject: newEvent to: attendees]; } + + return nil; } // @@ -551,14 +668,15 @@ // _updateAttendee:withDelegate:ownerUser:forEventUID:withRecurrenceId:withSequence:forUID:shouldAddSentBy: // // -- (void) saveComponent: (iCalEvent *) newEvent +- (NSException *) saveComponent: (iCalEvent *) newEvent { iCalEvent *oldEvent, *oldMasterEvent; - NSArray *attendees; NSCalendarDate *recurrenceId; NSString *recurrenceTime; SOGoUser *ownerUser; - + NSArray *attendees; + NSException *ex; + [[newEvent parent] setMethod: @""]; ownerUser = [SOGoUser userWithLogin: owner]; @@ -574,7 +692,11 @@ attendees = [newEvent attendeesWithoutUser: ownerUser]; if ([attendees count]) { - [self _handleAddedUsers: attendees fromEvent: newEvent]; + // We catch conflicts and abort the save process immediately + // in case of one with resources + if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent])) + return ex; + [self sendEMailUsingTemplateNamed: @"Invitation" forObject: [newEvent itipEntryWithMethod: @"request"] previousObject: nil @@ -586,6 +708,7 @@ else { BOOL hasOrganizer; + // Event is modified -- sent update status to all attendees // and modify their calendars. recurrenceId = [newEvent recurrenceId]; @@ -606,8 +729,10 @@ hasOrganizer = [[[oldMasterEvent organizer] email] length]; if (!hasOrganizer || [oldMasterEvent userIsOrganizer: ownerUser]) - // The owner is the organizer of the event; handle the modifications - [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]; + // The owner is the organizer of the event; handle the modifications. We aslo + // catch conflicts just like when the events are created + if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) + return ex; } [super saveComponent: newEvent]; @@ -618,6 +743,8 @@ safeCalendar = nil; [originalCalendar release]; originalCalendar = nil; + + return nil; } // @@ -1479,6 +1606,7 @@ // - (id) PUTAction: (WOContext *) _ctx { + NSException *ex; NSArray *roles; WORequest *rq; id response; @@ -1540,7 +1668,9 @@ attendees = [event attendeesWithoutUser: ownerUser]; if ([attendees count]) { - [self _handleAddedUsers: attendees fromEvent: event]; + if ((ex = [self _handleAddedUsers: attendees fromEvent: event])) + return ex; + [self sendEMailUsingTemplateNamed: @"Invitation" forObject: [event itipEntryWithMethod: @"request"] previousObject: nil @@ -1680,8 +1810,9 @@ if ([uid caseInsensitiveCompare: owner] == NSOrderedSame) { - [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]; - + if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) + return ex; + // A RECURRENCE-ID was removed so there has to be a change in the master event // We could also have an EXDATE added in the master component of the attendees // so we always compare the MASTER event. diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index 1cc355f7b..84ab6034f 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -60,7 +60,7 @@ toFolder: (SOGoGCSFolder *) newFolder; - (void) updateComponent: (iCalRepeatableEntityObject *) newObject; -- (void) saveComponent: (iCalRepeatableEntityObject *) newObject; +- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject; /* mail notifications */ - (void) sendEMailUsingTemplateNamed: (NSString *) pageName diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 5a0f5fd8d..8a295c3aa 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -655,13 +655,15 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence, } } -- (void) saveComponent: (iCalRepeatableEntityObject *) newObject +- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject { NSString *newiCalString; newiCalString = [[newObject parent] versitString]; [self saveContentString: newiCalString]; + + return nil; } /* raw saving */ diff --git a/SoObjects/Appointments/SOGoFreeBusyObject.h b/SoObjects/Appointments/SOGoFreeBusyObject.h index c136d25b0..1a09a38f9 100644 --- a/SoObjects/Appointments/SOGoFreeBusyObject.h +++ b/SoObjects/Appointments/SOGoFreeBusyObject.h @@ -1,14 +1,15 @@ /* + Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2000-2004 SKYRIX Software AG - This file is part of OGo + This file is part of SOGo - OGo is free software; you can redistribute it and/or modify it under + SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + SOGo 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. diff --git a/SoObjects/Appointments/SOGoFreeBusyObject.m b/SoObjects/Appointments/SOGoFreeBusyObject.m index 9b302bc6a..28e76b15c 100644 --- a/SoObjects/Appointments/SOGoFreeBusyObject.m +++ b/SoObjects/Appointments/SOGoFreeBusyObject.m @@ -1,14 +1,15 @@ /* + Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2000-2004 SKYRIX Software AG - This file is part of OGo + This file is part of SOGo - OGo is free software; you can redistribute it and/or modify it under + SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + SOGo 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. diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 2a0cc1085..3be52ee1d 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -68,6 +68,10 @@ BOOL passwordPolicy; NSMutableDictionary *_dnCache; + + /* resources handling */ + NSString *kindField; + NSString *multipleBookingsField; } - (void) setBindDN: (NSString *) newBindDN @@ -85,7 +89,9 @@ searchFields: (NSArray *) newSearchFields IMAPHostField: (NSString *) newIMAPHostField IMAPLoginField: (NSString *) newIMAPLoginField - andBindFields: (id) newBindFields; + bindFields: (id) newBindFields + kindField: (NSString *) newKindField +andMultipleBookingsField: (NSString *) newMultipleBookingsField; - (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID; - (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail; diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index b1c13c4dd..3ca5cce34 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -170,6 +170,9 @@ static NSArray *commonSearchFields; searchAttributes = nil; passwordPolicy = NO; + kindField = nil; + multipleBookingsField = nil; + _dnCache = [[NSMutableDictionary alloc] init]; } @@ -198,6 +201,8 @@ static NSArray *commonSearchFields; [searchAttributes release]; [domain release]; [_dnCache release]; + [kindField release]; + [multipleBookingsField release]; [super dealloc]; } @@ -226,7 +231,9 @@ static NSArray *commonSearchFields; searchFields: [udSource objectForKey: @"SearchFieldNames"] IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"] IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"] - andBindFields: [udSource objectForKey: @"bindFields"]]; + bindFields: [udSource objectForKey: @"bindFields"] + kindField: [udSource objectForKey: @"KindFieldName"] + andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]]; if ([sourceDomain length]) { @@ -309,7 +316,9 @@ static NSArray *commonSearchFields; searchFields: (NSArray *) newSearchFields IMAPHostField: (NSString *) newIMAPHostField IMAPLoginField: (NSString *) newIMAPLoginField - andBindFields: (id) newBindFields + bindFields: (id) newBindFields + kindField: (NSString *) newKindField +andMultipleBookingsField: (NSString *) newMultipleBookingsField { ASSIGN (baseDN, [newBaseDN lowercaseString]); if (newIDField) @@ -349,6 +358,10 @@ static NSArray *commonSearchFields; ASSIGN(bindFields, [newBindFields componentsSeparatedByString: @","]); } } + if (newKindField) + ASSIGN(kindField, newKindField); + if (newMultipleBookingsField) + ASSIGN(multipleBookingsField, newMultipleBookingsField); } - (BOOL) _setupEncryption: (NGLdapConnection *) encryptedConn @@ -704,6 +717,13 @@ static NSArray *commonSearchFields; // Add IMAP login from user defaults if ([IMAPLoginField length]) [searchAttributes addObjectUniquely: IMAPLoginField]; + + // Add the resources handling attributes + if ([kindField length]) + [searchAttributes addObjectUniquely: kindField]; + + if ([multipleBookingsField length]) + [searchAttributes addObjectUniquely: multipleBookingsField]; } return searchAttributes; @@ -855,6 +875,14 @@ static NSArray *commonSearchFields; [contactEntry setObject: [NSNumber numberWithInt: 1] forKey: @"isGroup"]; } + // We check if our entry is a resource. We also support + // determining resources based on the KindFieldName attribute + // value - see below. + else if ([classes containsObject: @"calendarresource"]) + { + [contactEntry setObject: [NSNumber numberWithInt: 1] + forKey: @"isResource"]; + } } while ((currentAttribute = [attributes nextObject])) @@ -864,7 +892,37 @@ static NSArray *commonSearchFields; // It's important here to set our attributes' key in lowercase. if (value) - [contactEntry setObject: value forKey: [currentAttribute lowercaseString]]; + { + currentAttribute = [currentAttribute lowercaseString]; + [contactEntry setObject: value forKey: currentAttribute]; + + // We check if that entry corresponds to a resource. For this, + // kindField must be defined and it must hold one of those values + // + // location + // thing + // group + // + if (kindField && + [kindField caseInsensitiveCompare: currentAttribute] == NSOrderedSame) + { + if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame || + [value caseInsensitiveCompare: @"thing"] == NSOrderedSame || + [value caseInsensitiveCompare: @"group"] == NSOrderedSame) + { + [contactEntry setObject: [NSNumber numberWithInt: 1] + forKey: @"isResource"]; + } + } + // We check for the number of simultanous bookings that is allowed. + // A value of 0 means that there's no limit. + if (multipleBookingsField && + [multipleBookingsField caseInsensitiveCompare: currentAttribute] == NSOrderedSame) + { + [contactEntry setObject: [NSNumber numberWithInt: [value intValue]] + forKey: @"numberOfSimultaneousBookings"]; + } + } } value = [[ldapEntry attributeWithName: IDField] stringValueAtIndex: 0]; diff --git a/SoObjects/SOGo/SOGoGroup.m b/SoObjects/SOGo/SOGoGroup.m index 3f15cda4c..023961c7b 100644 --- a/SoObjects/SOGo/SOGoGroup.m +++ b/SoObjects/SOGo/SOGoGroup.m @@ -1,6 +1,6 @@ /* SOGoGroup.m - this file is part of SOGo * - * Copyright (C) 2009-2010 Inverse inc. + * Copyright (C) 2009-2011 Inverse inc. * * Author: Ludovic Marcotte * diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 6a2ed994a..2a003e3cc 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2010 Inverse inc. + Copyright (C) 2006-2011 Inverse inc. Copyright (C) 2005 SKYRIX Software AG This file is part of SOGo. @@ -114,16 +114,17 @@ - (BOOL) isSuperUser; - (BOOL) canAuthenticate; +/* resource */ +- (BOOL) isResource; +- (int) numberOfSimultaneousBookings; + /* module access */ - (BOOL) canAccessModule: (NSString *) module; /* folders */ - - (SOGoUserFolder *) homeFolderInContext: (id) context; - - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context; -- (SOGoAppointmentFolder *) - personalCalendarFolderInContext: (WOContext *) context; +- (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context; @end diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 0e74924a8..1fcc859bb 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -766,6 +766,28 @@ return [authValue boolValue]; } +/* resource */ +- (BOOL) isResource +{ + NSNumber *v; + + v = [self _fetchFieldForUser: @"isResource"]; + + return (v && [v intValue]); +} + +- (int) numberOfSimultaneousBookings +{ + NSNumber *v; + + v = [self _fetchFieldForUser: @"numberOfSimultaneousBookings"]; + + if (v) + return [v intValue]; + + return 0; +} + /* module access */ - (BOOL) canAccessModule: (NSString *) module { diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index 5a4e13b56..36c55da2a 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -548,6 +548,9 @@ [contact setObject: [emails objectAtIndex: 0] forKey: @"c_email"]; } +// +// +// - (void) _fillContactInfosForUser: (NSMutableDictionary *) currentUser withUIDorEmail: (NSString *) uid { @@ -600,6 +603,15 @@ if (!access) [currentUser setObject: [NSNumber numberWithBool: NO] forKey: @"MailAccess"]; + + // We also fill the resource attributes, if any + if ([userEntry objectForKey: @"isResource"]) + [currentUser setObject: [userEntry objectForKey: @"isResource"] + forKey: @"isResource"]; + if ([userEntry objectForKey: @"numberOfSimultaneousBookings"]) + [currentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"] + forKey: @"numberOfSimultaneousBookings"]; + } } @@ -614,6 +626,7 @@ [currentUser setObject: c_imaphostname forKey: @"c_imaphostname"]; if (c_imaplogin) [currentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; + [currentUser setObject: emails forKey: @"emails"]; [currentUser setObject: cn forKey: @"cn"]; [currentUser setObject: c_uid forKey: @"c_uid"]; diff --git a/UI/MailerUI/German.lproj/Localizable.strings b/UI/MailerUI/German.lproj/Localizable.strings index c155a1994..1f09a38f8 100644 --- a/UI/MailerUI/German.lproj/Localizable.strings +++ b/UI/MailerUI/German.lproj/Localizable.strings @@ -165,8 +165,8 @@ "InboxFolderName" = "Posteingang"; "DraftsFolderName" = "Entwürfe"; "SieveFolderName" = "Filter"; -"OtherUsersFolderName" = "Other Users"; -"SharedFoldersName" = "Shared Folders"; +"Other Users" = "Andere Benutzer"; +"Shared Folders" = "Gemeinsame Ordner"; "Folders" = "Ordner"; /* title line */ /* MailMoveToPopUp */ diff --git a/UI/SOGoUI/UIxComponent.m b/UI/SOGoUI/UIxComponent.m index df69cb541..86a105f7d 100644 --- a/UI/SOGoUI/UIxComponent.m +++ b/UI/SOGoUI/UIxComponent.m @@ -1,14 +1,15 @@ /* + Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2004 SKYRIX Software AG - This file is part of OpenGroupware.org. + This file is part of SOGo - OGo is free software; you can redistribute it and/or modify it under + SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + SOGo 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. diff --git a/UI/SOGoUI/UIxJSClose.h b/UI/SOGoUI/UIxJSClose.h index 5a0820f78..41da7053a 100644 --- a/UI/SOGoUI/UIxJSClose.h +++ b/UI/SOGoUI/UIxJSClose.h @@ -1,6 +1,6 @@ /* UIxJSClose.h - this file is part of SOGo * - * Copyright (C) 2006 Inverse inc. + * Copyright (C) 2006-2011 Inverse inc. * * Author: Wolfgang Sourdeau * diff --git a/UI/SOGoUI/UIxJSClose.m b/UI/SOGoUI/UIxJSClose.m index 172e2b93a..936855232 100644 --- a/UI/SOGoUI/UIxJSClose.m +++ b/UI/SOGoUI/UIxJSClose.m @@ -1,6 +1,6 @@ /* UIxJSClose.m - this file is part of SOGo * - * Copyright (C) 2006 Inverse inc. + * Copyright (C) 2006-2011 Inverse inc. * * Author: Wolfgang Sourdeau * diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index b7a72bc60..5d07ecd6f 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -378,6 +378,7 @@ { SOGoAppointmentFolder *previousCalendar; SOGoAppointmentObject *co; + NSString *jsonResponse; SoSecurityManager *sm; NSException *ex; @@ -386,6 +387,7 @@ co = [co container]; previousCalendar = [co container]; sm = [SoSecurityManager sharedSecurityManager]; + ex = nil; if ([event hasRecurrenceRules]) [self _adjustRecurrentRules]; @@ -409,12 +411,12 @@ } // Save the event. - [co saveComponent: event]; + ex = [co saveComponent: event]; } else { // The event was modified -- save it. - [co saveComponent: event]; + ex = [co saveComponent: event]; if (componentCalendar && ![[componentCalendar ocsPath] @@ -432,8 +434,19 @@ } } } + + if (ex) + jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: + @"failure", @"status", + [ex reason], + @"message", + nil]; + else + jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: + @"success", @"status", nil]; - return [self jsCloseWithRefreshMethod: @"refreshEventsAndDisplay()"]; + return [self responseWithStatus: 200 + andString: [jsonResponse jsonRepresentation]]; } - (id ) viewAction diff --git a/UI/Templates/SchedulerUI/UIxComponentEditor.wox b/UI/Templates/SchedulerUI/UIxComponentEditor.wox index 3d7cbcecb..4ccd91a18 100644 --- a/UI/Templates/SchedulerUI/UIxComponentEditor.wox +++ b/UI/Templates/SchedulerUI/UIxComponentEditor.wox @@ -37,7 +37,7 @@ -
+