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>
* 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 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}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}\".";

View File

@ -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

View File

@ -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 <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 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__ */

View File

@ -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 <SOGo/SOGoUserManager.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+DAV.h>
#import <SOGo/SOGoObject.h>
#import <SOGo/SOGoPermissions.h>
@ -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.

View File

@ -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

View File

@ -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 */

View File

@ -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.

View File

@ -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.

View File

@ -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;

View File

@ -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];

View File

@ -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 <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
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

View File

@ -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
{

View File

@ -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"];

View File

@ -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 */

View File

@ -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.

View File

@ -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 <wsourdeau@inverse.ca>
*

View File

@ -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 <wsourdeau@inverse.ca>
*

View File

@ -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 <WOActionResults>) viewAction

View File

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

View File

@ -96,6 +96,8 @@ function validateAptEditor() {
}
}
AIM.submit($(document.editform), {'onComplete' : onEventPostComplete});
return true;
}
@ -176,6 +178,21 @@ function addContact(tag, fullContactName, contactId, contactName, contactEmail)
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) {
if (validateAptEditor()) {
document.forms['editform'].attendees.value = Object.toJSON($(attendees));