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
maint-2.0.2
Ludovic Marcotte 2011-04-25 10:31:08 +00:00
parent 326d376e6d
commit 12856abe4d
23 changed files with 340 additions and 74 deletions

View File

@ -1,3 +1,10 @@
2011-04-25 Ludovic Marcotte <lmarcotte@inverse.ca>
* 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 <lmarcotte@inverse.ca> 2011-04-21 Ludovic Marcotte <lmarcotte@inverse.ca>
* Added the possibility of translating IMAP namespaces * Added the possibility of translating IMAP namespaces

View File

@ -60,3 +60,6 @@ vtodo_class2 = "(Confidential task)";
= "%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."; = "%{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."
= "%{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}\".";

View File

@ -60,3 +60,6 @@ vtodo_class2 = "(Tâche confidentielle)";
= "%{Attendee} %{SentByText}a délégué votre invitation à %{Delegate}."; = "%{Attendee} %{SentByText}a délégué votre invitation à %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation." "%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}choisit de reporter sa décision par rapport à votre 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}\".";

View File

@ -2,14 +2,14 @@
Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2007-2011 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG 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 the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.
@ -623,6 +623,9 @@ static NSNumber *sharedYes = nil;
return record; return record;
} }
//
//
//
- (NSArray *) fixupRecords: (NSArray *) theRecords - (NSArray *) fixupRecords: (NSArray *) theRecords
{ {
// TODO: is the result supposed to be sorted by date? // TODO: is the result supposed to be sorted by date?
@ -802,6 +805,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
[self _fixExceptionRecord: newRecord fromRow: row]; [self _fixExceptionRecord: newRecord fromRow: row];
} }
//
//
//
- (void) _appendCycleExceptionsFromRow: (NSDictionary *) row - (void) _appendCycleExceptionsFromRow: (NSDictionary *) row
firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
forRange: (NGCalendarDateRange *) dateRange forRange: (NGCalendarDateRange *) dateRange

View File

@ -1,15 +1,14 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2007-2011 Inverse inc.
Copyright (C) 2007-2010 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 the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.
@ -25,17 +24,6 @@
#import <SOGo/SOGoContentObject.h> #import <SOGo/SOGoContentObject.h>
/*
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 NSArray;
@class NSException; @class NSException;
@class NSString; @class NSString;
@ -61,13 +49,6 @@
- (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients from: (NSString *) originator; - (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients from: (NSString *) originator;
- (NSArray *) postCalDAVEventCancelTo: (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 @end
#endif /* __Appointments_SOGoAppointmentObject_H__ */ #endif /* __Appointments_SOGoAppointmentObject_H__ */

View File

@ -2,7 +2,7 @@
Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2007-2011 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG 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 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 the terms of the GNU Lesser General Public License as published by the
@ -42,6 +42,7 @@
#import <SOGo/SOGoUserManager.h> #import <SOGo/SOGoUserManager.h>
#import <SOGo/NSArray+Utilities.h> #import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+DAV.h> #import <SOGo/NSObject+DAV.h>
#import <SOGo/SOGoObject.h> #import <SOGo/SOGoObject.h>
#import <SOGo/SOGoPermissions.h> #import <SOGo/SOGoPermissions.h>
@ -198,7 +199,6 @@
// We check if the attendee that was added to a single occurence is // 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 // present in the master component. If not, we add it with a participation
// status set to "DECLINED". // status set to "DECLINED".
user = [SOGoUser userWithLogin: theUID]; user = [SOGoUser userWithLogin: theUID];
person = [iCalPerson elementWithTag: @"attendee"]; person = [iCalPerson elementWithTag: @"attendee"];
[person setCn: [user cn]]; [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 // It also CHANGES the participation status of resources
fromEvent: (iCalEvent *) newEvent // 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; iCalPerson *currentAttendee;
NSEnumerator *enumerator;
NSString *currentUID; 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]; enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject])) while ((currentAttendee = [enumerator nextObject]))
@ -443,25 +546,31 @@
forUID: currentUID forUID: currentUID
owner: owner]; owner: owner];
} }
return nil;
} }
// //
// //
// //
- (void) _handleUpdatedEvent: (iCalEvent *) newEvent - (NSException *) _handleUpdatedEvent: (iCalEvent *) newEvent
fromOldEvent: (iCalEvent *) oldEvent fromOldEvent: (iCalEvent *) oldEvent
{ {
NSArray *attendees;
iCalEventChanges *changes; iCalEventChanges *changes;
NSArray *attendees;
NSException *ex;
changes = [newEvent getChangesRelativeToEvent: oldEvent]; changes = [newEvent getChangesRelativeToEvent: oldEvent];
if ([changes sequenceShouldBeIncreased]) if ([changes sequenceShouldBeIncreased])
{ {
// Set new attendees status to "needs action" and recompute changes when // 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]) if ([self _requireResponseFromAttendees: newEvent])
changes = [newEvent getChangesRelativeToEvent: oldEvent]; changes = [newEvent getChangesRelativeToEvent: oldEvent];
} }
attendees = [changes deletedAttendees]; attendees = [changes deletedAttendees];
if ([attendees count]) if ([attendees count])
{ {
@ -475,6 +584,10 @@
forObject: newEvent to: attendees]; forObject: newEvent to: attendees];
} }
if ((ex = [self _handleResourcesConflicts: [newEvent attendees]
forEvent: newEvent]))
return ex;
attendees = [changes insertedAttendees]; attendees = [changes insertedAttendees];
if ([changes sequenceShouldBeIncreased]) if ([changes sequenceShouldBeIncreased])
{ {
@ -516,7 +629,9 @@
if ([attendees count]) if ([attendees count])
{ {
// Send an invitation to new attendees // Send an invitation to new attendees
[self _handleAddedUsers: attendees fromEvent: newEvent]; if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent]))
return ex;
[self sendEMailUsingTemplateNamed: @"Invitation" [self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"] forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent previousObject: oldEvent
@ -524,6 +639,8 @@
[self sendReceiptEmailUsingTemplateNamed: @"Invitation" [self sendReceiptEmailUsingTemplateNamed: @"Invitation"
forObject: newEvent to: attendees]; forObject: newEvent to: attendees];
} }
return nil;
} }
// //
@ -551,14 +668,15 @@
// _updateAttendee:withDelegate:ownerUser:forEventUID:withRecurrenceId:withSequence:forUID:shouldAddSentBy: // _updateAttendee:withDelegate:ownerUser:forEventUID:withRecurrenceId:withSequence:forUID:shouldAddSentBy:
// //
// //
- (void) saveComponent: (iCalEvent *) newEvent - (NSException *) saveComponent: (iCalEvent *) newEvent
{ {
iCalEvent *oldEvent, *oldMasterEvent; iCalEvent *oldEvent, *oldMasterEvent;
NSArray *attendees;
NSCalendarDate *recurrenceId; NSCalendarDate *recurrenceId;
NSString *recurrenceTime; NSString *recurrenceTime;
SOGoUser *ownerUser; SOGoUser *ownerUser;
NSArray *attendees;
NSException *ex;
[[newEvent parent] setMethod: @""]; [[newEvent parent] setMethod: @""];
ownerUser = [SOGoUser userWithLogin: owner]; ownerUser = [SOGoUser userWithLogin: owner];
@ -574,7 +692,11 @@
attendees = [newEvent attendeesWithoutUser: ownerUser]; attendees = [newEvent attendeesWithoutUser: ownerUser];
if ([attendees count]) 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" [self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"] forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: nil previousObject: nil
@ -586,6 +708,7 @@
else else
{ {
BOOL hasOrganizer; BOOL hasOrganizer;
// Event is modified -- sent update status to all attendees // Event is modified -- sent update status to all attendees
// and modify their calendars. // and modify their calendars.
recurrenceId = [newEvent recurrenceId]; recurrenceId = [newEvent recurrenceId];
@ -606,8 +729,10 @@
hasOrganizer = [[[oldMasterEvent organizer] email] length]; hasOrganizer = [[[oldMasterEvent organizer] email] length];
if (!hasOrganizer || [oldMasterEvent userIsOrganizer: ownerUser]) if (!hasOrganizer || [oldMasterEvent userIsOrganizer: ownerUser])
// The owner is the organizer of the event; handle the modifications // The owner is the organizer of the event; handle the modifications. We aslo
[self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]; // catch conflicts just like when the events are created
if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]))
return ex;
} }
[super saveComponent: newEvent]; [super saveComponent: newEvent];
@ -618,6 +743,8 @@
safeCalendar = nil; safeCalendar = nil;
[originalCalendar release]; [originalCalendar release];
originalCalendar = nil; originalCalendar = nil;
return nil;
} }
// //
@ -1479,6 +1606,7 @@
// //
- (id) PUTAction: (WOContext *) _ctx - (id) PUTAction: (WOContext *) _ctx
{ {
NSException *ex;
NSArray *roles; NSArray *roles;
WORequest *rq; WORequest *rq;
id response; id response;
@ -1540,7 +1668,9 @@
attendees = [event attendeesWithoutUser: ownerUser]; attendees = [event attendeesWithoutUser: ownerUser];
if ([attendees count]) if ([attendees count])
{ {
[self _handleAddedUsers: attendees fromEvent: event]; if ((ex = [self _handleAddedUsers: attendees fromEvent: event]))
return ex;
[self sendEMailUsingTemplateNamed: @"Invitation" [self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"] forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil previousObject: nil
@ -1680,8 +1810,9 @@
if ([uid caseInsensitiveCompare: owner] == NSOrderedSame) 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 // 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 // We could also have an EXDATE added in the master component of the attendees
// so we always compare the MASTER event. // so we always compare the MASTER event.

View File

@ -60,7 +60,7 @@
toFolder: (SOGoGCSFolder *) newFolder; toFolder: (SOGoGCSFolder *) newFolder;
- (void) updateComponent: (iCalRepeatableEntityObject *) newObject; - (void) updateComponent: (iCalRepeatableEntityObject *) newObject;
- (void) saveComponent: (iCalRepeatableEntityObject *) newObject; - (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject;
/* mail notifications */ /* mail notifications */
- (void) sendEMailUsingTemplateNamed: (NSString *) pageName - (void) sendEMailUsingTemplateNamed: (NSString *) pageName

View File

@ -655,13 +655,15 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
} }
} }
- (void) saveComponent: (iCalRepeatableEntityObject *) newObject - (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject
{ {
NSString *newiCalString; NSString *newiCalString;
newiCalString = [[newObject parent] versitString]; newiCalString = [[newObject parent] versitString];
[self saveContentString: newiCalString]; [self saveContentString: newiCalString];
return nil;
} }
/* raw saving */ /* raw saving */

View File

@ -1,14 +1,15 @@
/* /*
Copyright (C) 2007-2011 Inverse inc.
Copyright (C) 2000-2004 SKYRIX Software AG 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 the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.

View File

@ -1,14 +1,15 @@
/* /*
Copyright (C) 2007-2011 Inverse inc.
Copyright (C) 2000-2004 SKYRIX Software AG 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 the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.

View File

@ -68,6 +68,10 @@
BOOL passwordPolicy; BOOL passwordPolicy;
NSMutableDictionary *_dnCache; NSMutableDictionary *_dnCache;
/* resources handling */
NSString *kindField;
NSString *multipleBookingsField;
} }
- (void) setBindDN: (NSString *) newBindDN - (void) setBindDN: (NSString *) newBindDN
@ -85,7 +89,9 @@
searchFields: (NSArray *) newSearchFields searchFields: (NSArray *) newSearchFields
IMAPHostField: (NSString *) newIMAPHostField IMAPHostField: (NSString *) newIMAPHostField
IMAPLoginField: (NSString *) newIMAPLoginField IMAPLoginField: (NSString *) newIMAPLoginField
andBindFields: (id) newBindFields; bindFields: (id) newBindFields
kindField: (NSString *) newKindField
andMultipleBookingsField: (NSString *) newMultipleBookingsField;
- (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID; - (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID;
- (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail; - (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail;

View File

@ -170,6 +170,9 @@ static NSArray *commonSearchFields;
searchAttributes = nil; searchAttributes = nil;
passwordPolicy = NO; passwordPolicy = NO;
kindField = nil;
multipleBookingsField = nil;
_dnCache = [[NSMutableDictionary alloc] init]; _dnCache = [[NSMutableDictionary alloc] init];
} }
@ -198,6 +201,8 @@ static NSArray *commonSearchFields;
[searchAttributes release]; [searchAttributes release];
[domain release]; [domain release];
[_dnCache release]; [_dnCache release];
[kindField release];
[multipleBookingsField release];
[super dealloc]; [super dealloc];
} }
@ -226,7 +231,9 @@ static NSArray *commonSearchFields;
searchFields: [udSource objectForKey: @"SearchFieldNames"] searchFields: [udSource objectForKey: @"SearchFieldNames"]
IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"] IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"]
IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"] IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"]
andBindFields: [udSource objectForKey: @"bindFields"]]; bindFields: [udSource objectForKey: @"bindFields"]
kindField: [udSource objectForKey: @"KindFieldName"]
andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]];
if ([sourceDomain length]) if ([sourceDomain length])
{ {
@ -309,7 +316,9 @@ static NSArray *commonSearchFields;
searchFields: (NSArray *) newSearchFields searchFields: (NSArray *) newSearchFields
IMAPHostField: (NSString *) newIMAPHostField IMAPHostField: (NSString *) newIMAPHostField
IMAPLoginField: (NSString *) newIMAPLoginField IMAPLoginField: (NSString *) newIMAPLoginField
andBindFields: (id) newBindFields bindFields: (id) newBindFields
kindField: (NSString *) newKindField
andMultipleBookingsField: (NSString *) newMultipleBookingsField
{ {
ASSIGN (baseDN, [newBaseDN lowercaseString]); ASSIGN (baseDN, [newBaseDN lowercaseString]);
if (newIDField) if (newIDField)
@ -349,6 +358,10 @@ static NSArray *commonSearchFields;
ASSIGN(bindFields, [newBindFields componentsSeparatedByString: @","]); ASSIGN(bindFields, [newBindFields componentsSeparatedByString: @","]);
} }
} }
if (newKindField)
ASSIGN(kindField, newKindField);
if (newMultipleBookingsField)
ASSIGN(multipleBookingsField, newMultipleBookingsField);
} }
- (BOOL) _setupEncryption: (NGLdapConnection *) encryptedConn - (BOOL) _setupEncryption: (NGLdapConnection *) encryptedConn
@ -704,6 +717,13 @@ static NSArray *commonSearchFields;
// Add IMAP login from user defaults // Add IMAP login from user defaults
if ([IMAPLoginField length]) if ([IMAPLoginField length])
[searchAttributes addObjectUniquely: IMAPLoginField]; [searchAttributes addObjectUniquely: IMAPLoginField];
// Add the resources handling attributes
if ([kindField length])
[searchAttributes addObjectUniquely: kindField];
if ([multipleBookingsField length])
[searchAttributes addObjectUniquely: multipleBookingsField];
} }
return searchAttributes; return searchAttributes;
@ -855,6 +875,14 @@ static NSArray *commonSearchFields;
[contactEntry setObject: [NSNumber numberWithInt: 1] [contactEntry setObject: [NSNumber numberWithInt: 1]
forKey: @"isGroup"]; 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])) while ((currentAttribute = [attributes nextObject]))
@ -864,7 +892,37 @@ static NSArray *commonSearchFields;
// It's important here to set our attributes' key in lowercase. // It's important here to set our attributes' key in lowercase.
if (value) 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]; value = [[ldapEntry attributeWithName: IDField] stringValueAtIndex: 0];

View File

@ -1,6 +1,6 @@
/* SOGoGroup.m - this file is part of SOGo /* SOGoGroup.m - this file is part of SOGo
* *
* Copyright (C) 2009-2010 Inverse inc. * Copyright (C) 2009-2011 Inverse inc.
* *
* Author: Ludovic Marcotte <lmarcotte@inverse.ca> * Author: Ludovic Marcotte <lmarcotte@inverse.ca>
* *

View File

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2006-2010 Inverse inc. Copyright (C) 2006-2011 Inverse inc.
Copyright (C) 2005 SKYRIX Software AG Copyright (C) 2005 SKYRIX Software AG
This file is part of SOGo. This file is part of SOGo.
@ -114,16 +114,17 @@
- (BOOL) isSuperUser; - (BOOL) isSuperUser;
- (BOOL) canAuthenticate; - (BOOL) canAuthenticate;
/* resource */
- (BOOL) isResource;
- (int) numberOfSimultaneousBookings;
/* module access */ /* module access */
- (BOOL) canAccessModule: (NSString *) module; - (BOOL) canAccessModule: (NSString *) module;
/* folders */ /* folders */
- (SOGoUserFolder *) homeFolderInContext: (id) context; - (SOGoUserFolder *) homeFolderInContext: (id) context;
- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context; - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context;
- (SOGoAppointmentFolder *) - (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context;
personalCalendarFolderInContext: (WOContext *) context;
@end @end

View File

@ -766,6 +766,28 @@
return [authValue boolValue]; 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 */ /* module access */
- (BOOL) canAccessModule: (NSString *) module - (BOOL) canAccessModule: (NSString *) module
{ {

View File

@ -548,6 +548,9 @@
[contact setObject: [emails objectAtIndex: 0] forKey: @"c_email"]; [contact setObject: [emails objectAtIndex: 0] forKey: @"c_email"];
} }
//
//
//
- (void) _fillContactInfosForUser: (NSMutableDictionary *) currentUser - (void) _fillContactInfosForUser: (NSMutableDictionary *) currentUser
withUIDorEmail: (NSString *) uid withUIDorEmail: (NSString *) uid
{ {
@ -600,6 +603,15 @@
if (!access) if (!access)
[currentUser setObject: [NSNumber numberWithBool: NO] [currentUser setObject: [NSNumber numberWithBool: NO]
forKey: @"MailAccess"]; 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"]; [currentUser setObject: c_imaphostname forKey: @"c_imaphostname"];
if (c_imaplogin) if (c_imaplogin)
[currentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; [currentUser setObject: c_imaplogin forKey: @"c_imaplogin"];
[currentUser setObject: emails forKey: @"emails"]; [currentUser setObject: emails forKey: @"emails"];
[currentUser setObject: cn forKey: @"cn"]; [currentUser setObject: cn forKey: @"cn"];
[currentUser setObject: c_uid forKey: @"c_uid"]; [currentUser setObject: c_uid forKey: @"c_uid"];

View File

@ -165,8 +165,8 @@
"InboxFolderName" = "Posteingang"; "InboxFolderName" = "Posteingang";
"DraftsFolderName" = "Entwürfe"; "DraftsFolderName" = "Entwürfe";
"SieveFolderName" = "Filter"; "SieveFolderName" = "Filter";
"OtherUsersFolderName" = "Other Users"; "Other Users" = "Andere Benutzer";
"SharedFoldersName" = "Shared Folders"; "Shared Folders" = "Gemeinsame Ordner";
"Folders" = "Ordner"; /* title line */ "Folders" = "Ordner"; /* title line */
/* MailMoveToPopUp */ /* MailMoveToPopUp */

View File

@ -1,14 +1,15 @@
/* /*
Copyright (C) 2007-2011 Inverse inc.
Copyright (C) 2004 SKYRIX Software AG 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 the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any Free Software Foundation; either version 2, or (at your option) any
later version. 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details. License for more details.

View File

@ -1,6 +1,6 @@
/* UIxJSClose.h - this file is part of SOGo /* UIxJSClose.h - this file is part of SOGo
* *
* Copyright (C) 2006 Inverse inc. * Copyright (C) 2006-2011 Inverse inc.
* *
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *

View File

@ -1,6 +1,6 @@
/* UIxJSClose.m - this file is part of SOGo /* UIxJSClose.m - this file is part of SOGo
* *
* Copyright (C) 2006 Inverse inc. * Copyright (C) 2006-2011 Inverse inc.
* *
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* *

View File

@ -378,6 +378,7 @@
{ {
SOGoAppointmentFolder *previousCalendar; SOGoAppointmentFolder *previousCalendar;
SOGoAppointmentObject *co; SOGoAppointmentObject *co;
NSString *jsonResponse;
SoSecurityManager *sm; SoSecurityManager *sm;
NSException *ex; NSException *ex;
@ -386,6 +387,7 @@
co = [co container]; co = [co container];
previousCalendar = [co container]; previousCalendar = [co container];
sm = [SoSecurityManager sharedSecurityManager]; sm = [SoSecurityManager sharedSecurityManager];
ex = nil;
if ([event hasRecurrenceRules]) if ([event hasRecurrenceRules])
[self _adjustRecurrentRules]; [self _adjustRecurrentRules];
@ -409,12 +411,12 @@
} }
// Save the event. // Save the event.
[co saveComponent: event]; ex = [co saveComponent: event];
} }
else else
{ {
// The event was modified -- save it. // The event was modified -- save it.
[co saveComponent: event]; ex = [co saveComponent: event];
if (componentCalendar if (componentCalendar
&& ![[componentCalendar ocsPath] && ![[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 <WOActionResults>) viewAction - (id <WOActionResults>) viewAction

View File

@ -37,7 +37,7 @@
</ul> </ul>
</div> </div>
<form var:href="saveURL" name="editform" onsubmit="return validateAptEditor();"> <form var:href="saveURL" name="editform">
<div id="eventView"> <div id="eventView">
<label><var:string label:value="Title:" /><span class="content" <label><var:string label:value="Title:" /><span class="content"
><input type="text" name="summary" id="summary" ><input type="text" name="summary" id="summary"

View File

@ -96,6 +96,8 @@ function validateAptEditor() {
} }
} }
AIM.submit($(document.editform), {'onComplete' : onEventPostComplete});
return true; return true;
} }
@ -176,6 +178,21 @@ function addContact(tag, fullContactName, contactId, contactName, contactEmail)
return false; return false;
} }
function onEventPostComplete(response) {
if (response && response.length > 0) {
var jsonResponse = response.evalJSON();
if (jsonResponse["status"] == "success") {
if (window.opener)
window.opener.refreshEventsAndDisplay();
window.close();
}
else {
var message = jsonResponse["message"];
alert(jsonResponse["message"]);
}
}
}
function saveEvent(sender) { function saveEvent(sender) {
if (validateAptEditor()) { if (validateAptEditor()) {
document.forms['editform'].attendees.value = Object.toJSON($(attendees)); document.forms['editform'].attendees.value = Object.toJSON($(attendees));