From 819c57de7f7711f25af4abc552f5168aec12f03a Mon Sep 17 00:00:00 2001 From: wolfgang Date: Tue, 4 Dec 2007 23:29:51 +0000 Subject: [PATCH] git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1287 d1b88da0-ebda-0310-925b-ed51d893ca5b --- ChangeLog | 59 +++++ Main/GNUmakefile | 3 +- Main/SOGo.m | 1 + .../Appointments/SOGoAppointmentFolder.m | 6 +- SoObjects/Mailer/SOGoDraftObject.m | 12 +- SoObjects/Mailer/SOGoMailBodyPart.m | 20 ++ SoObjects/SOGo/SOGoGCSFolder.m | 88 ++++++- SoObjects/SOGo/SOGoObject.m | 37 +++ SoObjects/SOGo/SOGoUserFolder.h | 7 + SoObjects/SOGo/SOGoUserFolder.m | 217 ++++++++++++++++++ UI/Common/UIxUserRightsEditor.m | 43 ++++ UI/Contacts/English.lproj/Localizable.strings | 3 + UI/Contacts/French.lproj/Localizable.strings | 2 + UI/Contacts/German.lproj/Localizable.strings | 3 + UI/Contacts/UIxContactFoldersView.m | 6 +- UI/MailerUI/UIxMailToSelection.m | 28 +-- UI/SOGoUI/SOGoACLAdvisory.h | 9 + UI/SOGoUI/SOGoACLAdvisory.m | 30 +++ .../English.lproj/Localizable.strings | 2 + UI/Scheduler/French.lproj/Localizable.strings | 2 + UI/Scheduler/German.lproj/Localizable.strings | 2 + .../ContactsUI/UIxContactsListView.wox | 2 +- .../SOGoACLEnglishAdditionAdvisory.wox | 5 + .../SOGoACLEnglishRemovalAdvisory.wox | 5 + .../SOGoACLFrenchAdditionAdvisory.wox | 11 +- UI/Templates/SOGoACLFrenchRemovalAdvisory.wox | 5 + .../SOGoACLGermanAdditionAdvisory.wox | 1 + UI/Templates/SOGoACLGermanRemovalAdvisory.wox | 1 + UI/WebServerResources/ContactsUI.js | 36 ++- UI/WebServerResources/SchedulerUI.js | 15 +- UI/WebServerResources/UIxMailToSelection.js | 16 +- 31 files changed, 620 insertions(+), 57 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9f43ee5e..37b7b4ae 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,62 @@ +2007-12-04 Wolfgang Sourdeau + + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([SOGoAppointmentFolder -davCalendarQuery:queryContext]): fixed a leak. + + * SoObjects/SOGo/SOGoGCSFolder.m ([SOGoGCSFolder -davSubscribe:localContext]) + ([SOGoGCSFolder -davUnsubscribe:localContext]): subscribe an + unsubscribe from DAV-based accesses. We could be compatible with + Microsoft's extensions but we have no need for a "subcription id", + so we implement our own. + + * SoObjects/SOGo/SOGoObject.m ([SOGoObject + -POSTAction:localContext]): new method to intercept DAV POSTs, + which we now use to implement certain custom commands such as + "subscribe" and "unsubscribe". + + * SoObjects/SOGo/SOGoUserFolder.m ([SOGoUserFolder + -davNamespaces]): declare the + "urn:inverse:params:xml:ns:inverse-dav" xml ns. + ([SOGoUserFolder -foldersOfType:folderTypeforUID:uid]): new method + designed to replace the UIxContactFoldersView.m mechanism for + displaying folders to subcribe to, as a common code base for both + Web and DAV-based subscriptions. + ([SOGoUserFolder -foldersOfType:typematchingUID:uid]): same as + above. + ([SOGoUserFolder -davCollectionQuery:queryContext]): new method + that implement a custom DAV-based protocol query for querying + folder based on specified attributes. + +2007-12-03 Ludovic Marcotte + + * Main/NSException+Stacktrace.{h,m} - new files + to handle automatic stack trace generation + upon an uncaught exception. + + * Updated the templates and Localizable.string files + to fix typos and add new strings. + + * SoObjects/Mailer/SOGoMailBodyPart.m + Fixed attachment retreival when the first character + is a digit. + + * SoObjects/SOGo/SOGoGCSFolder.m + Fixed the sending of emails when folders are created/removed. + Notifications are sent if the defaults SOGoFoldersSendEMailNotifications + is set to YES. + + * UI/Common/UIxUserRightsEditor.m + * UI/Templates/SOGoACLEnglishModificationAdvisory.wox + * UI/Templates/SOGoACLFrenchModificationAdvisory.wox + * UI/Templates/SOGoACLGermanModificationAdvisory.wox + Added the capabilities to email notifications when ACLs have + changed on a DAV collection or an IMAP mailbox. Also added + new templates (3 .wox) to deal with this. + + * UI/WebServerResources/ContactsUI.js + UI/WebServerResources/SchedulerUI.js + Added warnings on operations w/o selection. + 2007-11-30 Wolfgang Sourdeau * SoObjects/SOGo/SOGoParentFolder.m ([SOGoParentFolder diff --git a/Main/GNUmakefile b/Main/GNUmakefile index 53a8adcf..cac596f5 100644 --- a/Main/GNUmakefile +++ b/Main/GNUmakefile @@ -6,7 +6,7 @@ include ../Version include ./Version ADDITIONAL_INCLUDE_DIRS += -I../SOPE/ -ADDITIONAL_LIB_DIRS += -L../SOPE/GDLContentStore/obj/ +ADDITIONAL_LIB_DIRS += -L../SOPE/GDLContentStore/obj/ -lbfd SOGOD = sogod-$(MAJOR_VERSION).$(MINOR_VERSION) TOOL_NAME = $(SOGOD) @@ -17,6 +17,7 @@ all:: $(SOGOD)_OBJC_FILES += \ sogod.m \ + NSException+Stacktrace.m\ SOGo.m \ SOGoProductLoader.m \ build.m diff --git a/Main/SOGo.m b/Main/SOGo.m index 32be4ced..6bdb86e3 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -51,6 +51,7 @@ #import "build.h" #import "SOGoProductLoader.h" +#import "NSException+Stacktrace.h" @interface SOGo : SoApplication { diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index f4753e7c..6deb3e3c 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -238,8 +238,7 @@ static NSNumber *sharedYes = nil; NSMutableArray *filters; NSDictionary *filter; - filters = [NSMutableArray new]; - + filters = [NSMutableArray array]; children = [[parentNode getElementsByTagName: @"comp-filter"] objectEnumerator]; node = [children nextObject]; @@ -268,8 +267,7 @@ static NSNumber *sharedYes = nil; max = [filters count]; for (count = 0; count < max; count++) { -#warning huh? why not objectAtIndex: count? - currentFilter = [filters objectAtIndex: 0]; + currentFilter = [filters objectAtIndex: count]; apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"] to: [currentFilter objectForKey: @"end"] title: [currentFilter objectForKey: @"title"] diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 0ebb3432..4aac23de 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -467,7 +467,6 @@ static BOOL showTextAttachmentsInline = NO; [_info setObject: to forKey: @"to"]; /* CC processing if we reply-to-all: add all 'to' and 'cc' */ - if (_replyToAll) { to = [NSMutableArray new]; @@ -498,6 +497,17 @@ static BOOL showTextAttachmentsInline = NO; [self _addEMailsOfAddresses: [_envelope from] toArray: to]; } + /* If we have no To but we have Cc recipients, let's move the Cc + to the To bucket... */ + if ([[_info objectForKey: @"to"] count] == 0 && [_info objectForKey: @"cc"]) + { + id o; + + o = [_info objectForKey: @"cc"]; + [_info setObject: o forKey: @"to"]; + [_info removeObjectForKey: @"cc"]; + } + [allRecipients release]; [addrs release]; } diff --git a/SoObjects/Mailer/SOGoMailBodyPart.m b/SoObjects/Mailer/SOGoMailBodyPart.m index c3bcf78d..c60f34b9 100644 --- a/SoObjects/Mailer/SOGoMailBodyPart.m +++ b/SoObjects/Mailer/SOGoMailBodyPart.m @@ -144,6 +144,26 @@ static BOOL debugOn = NO; return [clazz objectWithName: _key inContainer: self]; } +/* We overwrite the super's class method in order to make sure + we aren't dealing with our actual filename as the _key. That + could lead to problems if we weren't doing this as our filename + could start with a digit, leading to a wrong assumption in + the super class +*/ +- (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx +{ + NSString *s; + + s = [[[self partInfo] objectForKey: @"parameterList"] objectForKey: @"name"]; + + if (!s) + s = [[[[self partInfo] objectForKey: @"disposition"] objectForKey: @"parameterList"] objectForKey: @"filename"]; + + if (s && [s isEqualToString: _key]) return NO; + + return [super isBodyPartKey: _key inContext: _ctx]; +} + - (id) lookupName: (NSString *) _key inContext: (id) _ctx acquire: (BOOL) _flag diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 69e2a356..b1f0e6df 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -25,12 +25,14 @@ #import #import #import +#import #import #import #import #import #import +#import #import #import #import @@ -55,9 +57,18 @@ #import "SOGoGCSFolder.h" static NSString *defaultUserID = @""; +static BOOL sendFolderAdvisories = NO; @implementation SOGoGCSFolder ++ (void) initialize +{ + NSUserDefaults *ud; + + ud = [NSUserDefaults standardUserDefaults]; + sendFolderAdvisories = [ud boolForKey: @"SOGoFoldersSendEMailNotifications"]; +} + + (id) folderWithSubscriptionReference: (NSString *) reference inContainer: (id) aContainer { @@ -254,8 +265,6 @@ static NSString *defaultUserID = @""; [page send]; } -// if (!result) [self sendFolderAdvisoryTemplate: @"Addition"]; - - (BOOL) create { NSException *result; @@ -264,6 +273,8 @@ static NSString *defaultUserID = @""; withName: displayName atPath: ocsPath]; + if (!result && sendFolderAdvisories) [self sendFolderAdvisoryTemplate: @"Addition"]; + return (result == nil); } @@ -280,11 +291,11 @@ static NSString *defaultUserID = @""; else error = [[self folderManager] deleteFolderAtPath: ocsPath]; + if (!error && sendFolderAdvisories) [self sendFolderAdvisoryTemplate: @"Removal"]; + return error; } -// if (!error) [self sendFolderAdvisoryTemplate: @"Removal"]; - - (void) renameTo: (NSString *) newName { GCSChannelManager *cm; @@ -423,6 +434,75 @@ static NSString *defaultUserID = @""; return names; } +#warning this code should be cleaned up +#warning this code is a dup of UIxFolderActions,\ + we should remove the methods there instead +- (WOResponse *) _subscribe: (BOOL) reallyDo + inContext: (WOContext *) localContext +{ + WOResponse *response; + NSMutableArray *folderSubscription; + NSString *subscriptionPointer, *baseFolder, *folder; + SOGoUser *activeUser; + NSUserDefaults *ud; + NSArray *realFolderPath; + NSMutableDictionary *moduleSettings; + + activeUser = [localContext activeUser]; + ud = [activeUser userSettings]; + baseFolder = [container nameInContainer]; + moduleSettings = [ud objectForKey: baseFolder]; + + response = [localContext response]; + if ([owner isEqualToString: [activeUser login]]) + { + [response setStatus: 403]; + [response appendContentString: + @"You cannot (un)subscribe to a folder that you own!"]; + } + else + { + folderSubscription + = [moduleSettings objectForKey: @"SubscribedFolders"]; + if (!(folderSubscription + && [folderSubscription isKindOfClass: [NSMutableArray class]])) + { + folderSubscription = [NSMutableArray array]; + [moduleSettings setObject: folderSubscription + forKey: @"SubscribedFolders"]; + } + + realFolderPath = [nameInContainer componentsSeparatedByString: @"_"]; + if ([realFolderPath count] > 1) + folder = [realFolderPath objectAtIndex: 1]; + else + folder = [realFolderPath objectAtIndex: 0]; + + subscriptionPointer = [NSString stringWithFormat: @"%@:%@/%@", + owner, baseFolder, folder]; + if (reallyDo) + [folderSubscription addObjectUniquely: subscriptionPointer]; + else + [folderSubscription removeObject: subscriptionPointer]; + + [ud synchronize]; + + [response setStatus: 204]; + } + + return response; +} + +- (id ) davSubscribe: (WOContext *) localContext +{ + return [self _subscribe: YES inContext: localContext]; +} + +- (id ) davUnsubscribe: (WOContext *) localContext +{ + return [self _subscribe: NO inContext: localContext]; +} + /* acls as a container */ - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray; diff --git a/SoObjects/SOGo/SOGoObject.m b/SoObjects/SOGo/SOGoObject.m index ff29a3c4..a9a49003 100644 --- a/SoObjects/SOGo/SOGoObject.m +++ b/SoObjects/SOGo/SOGoObject.m @@ -46,6 +46,7 @@ #import #import #import +#import #import #import @@ -646,6 +647,42 @@ static BOOL kontactGroupDAV = YES; return response; } +- (NSString *) _parseXMLCommand: (id ) document +{ + NSString *command; + + command = [[document firstChild] nodeName]; + + return [NSString stringWithFormat: @"%@:", command]; +} + +- (id) POSTAction: (id) localContext +{ + id obj; + NSString *cType, *command; + id document; + SEL commandSel; + WORequest *rq; + + obj = nil; + + rq = [localContext request]; + if ([rq isSoWebDAVRequest]) + { + cType = [rq headerForKey: @"content-type"]; + if ([cType isEqualToString: @"application/xml"]) + { + document = [rq contentAsDOMDocument]; + command = [[self _parseXMLCommand: document] davMethodToObjC]; + commandSel = NSSelectorFromString (command); + if ([self respondsToSelector: commandSel]) + obj = [self performSelector: commandSel withObject: localContext]; + } + } + + return obj; +} + - (id) GETAction: (id) localContext { // TODO: I guess this should really be done by SOPE (redirect to diff --git a/SoObjects/SOGo/SOGoUserFolder.h b/SoObjects/SOGo/SOGoUserFolder.h index 970f5e84..ccb30545 100644 --- a/SoObjects/SOGo/SOGoUserFolder.h +++ b/SoObjects/SOGo/SOGoUserFolder.h @@ -36,6 +36,8 @@ /SOGo/so/znek/Calendar */ +@class NSArray; +@class NSDictionary; @class NSString; @class WOContext; @@ -49,6 +51,11 @@ - (NSString *) ownerInContext: (WOContext *) _ctx; +- (NSArray *) foldersOfType: (NSString *) folderType + forUID: (NSString *) uid; +- (NSDictionary *) foldersOfType: (NSString *) type + matchingUID: (NSString *) uid; + /* TODO: not implemented, bad bad */ // - (id)lookupFreeBusyObject; diff --git a/SoObjects/SOGo/SOGoUserFolder.m b/SoObjects/SOGo/SOGoUserFolder.m index 035cbcb4..688d3f04 100644 --- a/SoObjects/SOGo/SOGoUserFolder.m +++ b/SoObjects/SOGo/SOGoUserFolder.m @@ -21,18 +21,29 @@ #import #import +#import #import #import +#import #import +#import #import +#import +#import +#import +#import +#import +#import #import #import #import #import +#import "NSDictionary+Utilities.h" +#import "LDAPUserManager.h" #import "SOGoPermissions.h" #import "SOGoUser.h" @@ -96,6 +107,212 @@ return self; } +- (NSArray *) davNamespaces +{ + return [NSArray arrayWithObject: @"urn:inverse:params:xml:ns:inverse-dav"]; +} + +- (NSDictionary *) _parseCollectionFilters: (id ) parentNode +{ + NSEnumerator *children; + NGDOMNode *node; + NSMutableDictionary *filter; + NSString *componentName; + + filter = [NSMutableDictionary dictionaryWithCapacity: 2]; + children = [[parentNode getElementsByTagName: @"prop-match"] + objectEnumerator]; + while ((node = [children nextObject])) + { + componentName = [[node attribute: @"name"] lowercaseString]; + [filter setObject: [node textValue] forKey: componentName]; + } + + return filter; +} + +#warning UIxContactsFoldersView should use these methods\ + instead from now on... + +- (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder +{ + NSMutableArray *folders; + NSEnumerator *subfolders; + SOGoFolder *currentFolder; + NSString *folderName; + NSMutableDictionary *currentDictionary; + SoSecurityManager *securityManager; + + securityManager = [SoSecurityManager sharedSecurityManager]; + + folders = [NSMutableArray array]; + + subfolders = [[parentFolder subFolders] objectEnumerator]; + while ((currentFolder = [subfolders nextObject])) + { + if (![securityManager validatePermission: SOGoPerm_AccessObject + onObject: currentFolder inContext: context]) + { + folderName = [NSString stringWithFormat: @"/%@/%@", + [parentFolder nameInContainer], + [currentFolder nameInContainer]]; + currentDictionary + = [NSMutableDictionary dictionaryWithCapacity: 3]; + [currentDictionary setObject: [currentFolder displayName] + forKey: @"displayName"]; + [currentDictionary setObject: folderName forKey: @"name"]; + [currentDictionary setObject: [currentFolder folderType] + forKey: @"type"]; + [folders addObject: currentDictionary]; + } + } + + return folders; +} + +- (NSArray *) foldersOfType: (NSString *) folderType + forUID: (NSString *) uid +{ + NSObject *userFolder; + SOGoParentFolder *parentFolder; + NSMutableArray *folders; + + folders = [NSMutableArray array]; + + userFolder = [container lookupName: uid inContext: context acquire: NO]; + + /* FIXME: should be moved in the SOGo* classes. Maybe by having a SOGoFolderManager. */ + if ([folderType length] == 0 || [folderType isEqualToString: @"calendar"]) + { + parentFolder = [userFolder lookupName: @"Calendar" + inContext: context acquire: NO]; + [folders + addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]]; + } + if ([folderType length] == 0 || [folderType isEqualToString: @"contact"]) + { + parentFolder = [userFolder lookupName: @"Contacts" + inContext: context acquire: NO]; + [folders + addObjectsFromArray: [self _subFoldersFromFolder: parentFolder]]; + } + + return folders; +} + +- (NSDictionary *) foldersOfType: (NSString *) type + matchingUID: (NSString *) uid +{ + NSArray *contacts, *folders; + NSEnumerator *enumerator; + NSDictionary *contact; + NSMutableDictionary *results; + + results = [NSMutableDictionary dictionary]; + + contacts + = [[LDAPUserManager sharedUserManager] fetchContactsMatching: uid]; + enumerator = [contacts objectEnumerator]; + while ((contact = [enumerator nextObject])) + { + uid = [contact objectForKey: @"c_uid"]; + folders = [self foldersOfType: type + forUID: [contact objectForKey: @"c_uid"]]; + [results setObject: folders forKey: contact]; + } + + return results; +} + +- (NSString *) _baseDAVURLWithSuffix: (NSString *) suffix +{ + NSURL *prefixURL; + + prefixURL = [NSURL URLWithString: [NSString stringWithFormat: @"../%@", suffix] + relativeToURL: [self davURL]]; + + return [[prefixURL standardizedURL] absoluteString]; +} + +- (void) _appendFolders: (NSDictionary *) users + toResponse: (WOResponse *) r +{ + NSDictionary *currentContact, *currentFolder; + NSEnumerator *keys, *folders; + NSString *baseHREF, *data; + + baseHREF = [self _baseDAVURLWithSuffix: @"./"]; + + keys = [[users allKeys] objectEnumerator]; + while ((currentContact = [keys nextObject])) + { + folders = [[users objectForKey: currentContact] objectEnumerator]; + while ((currentFolder = [folders nextObject])) + { + [r appendContentString: @""]; + data = [NSString stringWithFormat: @"%@%@%@", baseHREF, + [currentContact objectForKey: @"c_uid"], + [currentFolder objectForKey: @"name"]]; + [r appendContentString: data]; + [r appendContentString: @""]; + [r appendContentString: @"HTTP/1.1 200 OK"]; + [r appendContentString: @""]; + data = [NSString stringWithFormat: @"%@users/%@", baseHREF, + [currentContact objectForKey: @"c_uid"]]; + [r appendContentString: data]; + [r appendContentString: @""]; + data = [currentContact keysWithFormat: @"%{cn} <%{c_email}>"]; + [r appendContentString: [data stringByEscapingXMLString]]; + [r appendContentString: @""]; + data = [currentFolder objectForKey: @"displayName"]; + [r appendContentString: [data stringByEscapingXMLString]]; + [r appendContentString: @"\r\n"]; + } + } +} + +- (void) _appendCollectionsMatchingFilter: (NSDictionary *) filter + toResponse: (WOResponse *) r +{ + NSString *prefix, *queryOwner, *uid; + NSDictionary *folders; + + prefix = [self _baseDAVURLWithSuffix: @"users/"]; + queryOwner = [filter objectForKey: @"owner"]; + if ([queryOwner hasPrefix: prefix]) + { + uid = [queryOwner substringFromIndex: [prefix length]]; + folders = [self foldersOfType: [filter objectForKey: @"resource-type"] + matchingUID: uid]; + [self _appendFolders: folders toResponse: r]; + } +} + +- (id) davCollectionQuery: (id) queryContext +{ + WOResponse *r; + NSDictionary *filter; + id document; + + r = [context response]; + [r setStatus: 207]; + [r setContentEncoding: NSUTF8StringEncoding]; + [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; + [r setHeader: @"no-cache" forKey: @"pragma"]; + [r setHeader: @"no-cache" forKey: @"cache-control"]; + [r appendContentString:@"\r\n"]; + [r appendContentString: @"\r\n"]; + + document = [[context request] contentAsDOMDocument]; + filter = [self _parseCollectionFilters: document]; + [self _appendCollectionsMatchingFilter: filter toResponse: r]; + + [r appendContentString:@"\r\n"]; + + return r; +} + // - (SOGoGroupsFolder *) lookupGroupsFolder // { // return [self lookupName: @"Groups" inContext: nil acquire: NO]; diff --git a/UI/Common/UIxUserRightsEditor.m b/UI/Common/UIxUserRightsEditor.m index ad674f63..d447ae84 100644 --- a/UI/Common/UIxUserRightsEditor.m +++ b/UI/Common/UIxUserRightsEditor.m @@ -21,16 +21,30 @@ */ #import +#import #import #import #import #import #import +#import +#import +#import #import "UIxUserRightsEditor.h" +static BOOL sendACLAdvisories = NO; + @implementation UIxUserRightsEditor ++ (void) initialize +{ + NSUserDefaults *ud; + + ud = [NSUserDefaults standardUserDefaults]; + sendACLAdvisories = [ud boolForKey: @"SOGoACLsSendEMailNotifications"]; +} + - (id) init { if ((self = [super init])) @@ -121,6 +135,25 @@ return response; } +- (void) sendACLAdvisoryTemplateForObject: (id) theObject +{ + NSString *language, *pageName; + SOGoUser *user; + SOGoACLAdvisory *page; + WOApplication *app; + + user = [SOGoUser userWithLogin: uid roles: nil]; + language = [user language]; + pageName = [NSString stringWithFormat: @"SOGoACL%@ModificationAdvisory", + language]; + + app = [WOApplication application]; + page = [app pageWithName: pageName inContext: context]; + [page setACLObject: theObject]; + [page setRecipientUID: uid]; + [page send]; +} + - (id ) saveUserRightsAction { id response; @@ -130,8 +163,18 @@ reason: @"No such user."]; else { + NSArray *o; + + o = [NSArray arrayWithArray: userRights]; + [self updateRights]; [[self clientObject] setRoles: userRights forUser: uid]; + + if (![o isEqualToArray: userRights] && sendACLAdvisories) + { + [self sendACLAdvisoryTemplateForObject: [self clientObject]]; + } + response = [self jsCloseWithRefreshMethod: nil]; } diff --git a/UI/Contacts/English.lproj/Localizable.strings b/UI/Contacts/English.lproj/Localizable.strings index f1dbba2e..9d17ab3e 100644 --- a/UI/Contacts/English.lproj/Localizable.strings +++ b/UI/Contacts/English.lproj/Localizable.strings @@ -31,6 +31,7 @@ "edit" = "edit"; "invalidemailwarn" = "invalidemailwarn"; "new" = "new"; +"Preferred Phone" = "Preferred Phone"; /* Folders */ "Personal Address Book" = "Personal Address Book"; @@ -131,3 +132,5 @@ "The selected contact has no email address." = "The selected contact has no email address."; + +"Please select a contact." = "Please select a contact."; diff --git a/UI/Contacts/French.lproj/Localizable.strings b/UI/Contacts/French.lproj/Localizable.strings index cb2df47d..4854e485 100644 --- a/UI/Contacts/French.lproj/Localizable.strings +++ b/UI/Contacts/French.lproj/Localizable.strings @@ -38,6 +38,7 @@ "edit" = "Éditer"; "invalidemailwarn" = "Champ de l'email invalide, continuer quand même ?"; "new" = "Nouveau"; +"Preferred Phone" = "Numéro préféré"; /* Folders */ "Personal Address Book" = "Carnet d'adresses personnel"; @@ -145,3 +146,4 @@ "The selected contact has no email address." = "Cette personne n'a pas d'adresse courriel."; +"Please select a contact." = "Veuillez sélectionner un contact.."; \ No newline at end of file diff --git a/UI/Contacts/German.lproj/Localizable.strings b/UI/Contacts/German.lproj/Localizable.strings index b220e74f..c5ffe0bc 100644 --- a/UI/Contacts/German.lproj/Localizable.strings +++ b/UI/Contacts/German.lproj/Localizable.strings @@ -38,6 +38,7 @@ "edit" = "Éditer"; "invalidemailwarn" = "Champ de l'email invalide, continuer quand même ?"; "new" = "Neu"; +"Preferred Phone" = "FIXME"; /* Folders */ "Personal Address Book" = "Personal Address Book"; @@ -135,3 +136,5 @@ "The selected contact has no email address." = "The selected contact has no email address."; + +"Please select a contact." = "Bitte einen Kontakt auswählen."; \ No newline at end of file diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index 9d1f87a1..9605ed56 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -190,7 +190,7 @@ SoSecurityManager *securityManager; securityManager = [SoSecurityManager sharedSecurityManager]; - + // return (([securityManager validatePermission: SoPerm_AccessContentsInformation // onObject: contactFolder // inContext: context] == nil) @@ -294,8 +294,7 @@ responseString = [NSMutableString new]; contacts = [results objectEnumerator]; - contact = [contacts nextObject]; - while (contact) + while ((contact = [contacts nextObject])) { uid = [contact objectForKey: @"c_uid"]; folders = [self _foldersForUID: uid ofType: folderType]; @@ -305,7 +304,6 @@ [contact objectForKey: @"cn"], [contact objectForKey: @"c_email"], foldersString]; - contact = [contacts nextObject]; } [response appendContentString: responseString]; [responseString release]; diff --git a/UI/MailerUI/UIxMailToSelection.m b/UI/MailerUI/UIxMailToSelection.m index c241105e..8e53fa40 100644 --- a/UI/MailerUI/UIxMailToSelection.m +++ b/UI/MailerUI/UIxMailToSelection.m @@ -86,7 +86,7 @@ static NSArray *headers = nil; - (id) init { if ((self = [super init])) - currentIndex = 0; + currentIndex = -1; return self; } @@ -213,13 +213,23 @@ static NSArray *headers = nil; /* identifiers */ +- (NSString *) nextId +{ + currentIndex++; + + return @""; +} + - (NSString *) currentRowId { + [self nextId]; + return [NSString stringWithFormat: @"row_%d", currentIndex]; } - (NSString *) currentPopUpId { + return [NSString stringWithFormat: @"popup_%d", currentIndex]; } @@ -228,13 +238,6 @@ static NSArray *headers = nil; return [NSString stringWithFormat: @"addr_%d", currentIndex]; } -- (NSString *) nextId -{ - currentIndex++; - - return @""; -} - /* handling requests */ - (void) _fillAddresses: (NSMutableArray *) addresses @@ -326,13 +329,4 @@ static NSArray *headers = nil; return [to count] + [cc count] + [bcc count]; } -- (int) currentIndex -{ - int count; - - count = [self addressCount]; - - return count > 0 ? count - 1 : 0; -} - @end /* UIxMailToSelection */ diff --git a/UI/SOGoUI/SOGoACLAdvisory.h b/UI/SOGoUI/SOGoACLAdvisory.h index 762b89a1..d991dbad 100644 --- a/UI/SOGoUI/SOGoACLAdvisory.h +++ b/UI/SOGoUI/SOGoACLAdvisory.h @@ -68,6 +68,15 @@ @interface SOGoACLGermanAdditionAdvisory : SOGoACLAdditionAdvisory @end +@interface SOGoACLEnglishModificationAdvisory : SOGoACLAdditionAdvisory +@end + +@interface SOGoACLFrenchModificationAdvisory : SOGoACLAdditionAdvisory +@end + +@interface SOGoACLGermanModificationAdvisory : SOGoACLAdditionAdvisory +@end + @interface SOGoACLEnglishRemovalAdvisory : SOGoACLRemovalAdvisory @end diff --git a/UI/SOGoUI/SOGoACLAdvisory.m b/UI/SOGoUI/SOGoACLAdvisory.m index b52ebcc1..97994ae3 100644 --- a/UI/SOGoUI/SOGoACLAdvisory.m +++ b/UI/SOGoUI/SOGoACLAdvisory.m @@ -97,6 +97,21 @@ return url; } +- (NSString *) httpFolderURL +{ + NSString *absoluteString; + NSMutableString *url; + +#warning the url returned by SOGoMail may be empty, we need to handle that + absoluteString = [[aclObject soURL] absoluteString]; + url = [NSMutableString stringWithString: absoluteString]; + + if (![url hasSuffix: @"/"]) + [url appendString: @"/"]; + + return url; +} + - (NSString *) resourceName { return [aclObject nameInContainer]; @@ -223,6 +238,12 @@ @end +@implementation SOGoACLModificationAdvisory + +- (NSString *) aclMethod { return @"modify"; } + +@end + @implementation SOGoACLEnglishAdditionAdvisory @end @@ -232,6 +253,15 @@ @implementation SOGoACLGermanAdditionAdvisory @end +@implementation SOGoACLEnglishModificationAdvisory +@end + +@implementation SOGoACLFrenchModificationAdvisory +@end + +@implementation SOGoACLGermanModificationAdvisory +@end + @implementation SOGoACLEnglishRemovalAdvisory @end diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 494b6d74..c6e1c5b0 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -423,3 +423,5 @@ vtodo_class2 = "(Confidential task)"; "closeThisWindowMessage" = "Thank you! You may now close this window or view your "; "Multicolumn Day View" = "Multicolumn Day View"; + +"Please select an event or a task." = "Please select an event or a task."; \ No newline at end of file diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index 1688824d..725f0358 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -422,3 +422,5 @@ vtodo_class2 = "(Tâche confidentielle)"; "closeThisWindowMessage" = "Merci! Vous pouvez maintenant fermer cette fenêtre ou consulter votre "; "Multicolumn Day View" = "Multicolonne"; + +"Please select an event or a task." = "Veuillez sélectionner un événement ou une tâche."; \ No newline at end of file diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index 1f65637c..15ab3145 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -411,3 +411,5 @@ vtodo_class2 = "(Confidential task)"; "closeThisWindowMessage" = "Vielen Dank! Sie können dieses Fenster jetzt schließen."; "Multicolumn Day View" = "Multicolonne"; + +"Please select an event or a task." = "Veuillez sélectionner un événement ou une tâche."; \ No newline at end of file diff --git a/UI/Templates/ContactsUI/UIxContactsListView.wox b/UI/Templates/ContactsUI/UIxContactsListView.wox index efc8c859..0593bb3f 100644 --- a/UI/Templates/ContactsUI/UIxContactsListView.wox +++ b/UI/Templates/ContactsUI/UIxContactsListView.wox @@ -23,7 +23,7 @@ > diff --git a/UI/Templates/SOGoACLEnglishAdditionAdvisory.wox b/UI/Templates/SOGoACLEnglishAdditionAdvisory.wox index b15cd0da..250cb518 100644 --- a/UI/Templates/SOGoACLEnglishAdditionAdvisory.wox +++ b/UI/Templates/SOGoACLEnglishAdditionAdvisory.wox @@ -13,10 +13,15 @@ has added you to the access list for his '' folder. + You can subscribe directly to that folder by following this link: subscribe?mail-invitation=YES Otherwise, you will be able to subscribe later from the SOGo web interface. + +You can also access this resource remotely using the following URL: + + diff --git a/UI/Templates/SOGoACLEnglishRemovalAdvisory.wox b/UI/Templates/SOGoACLEnglishRemovalAdvisory.wox index eff944b0..6a400935 100644 --- a/UI/Templates/SOGoACLEnglishRemovalAdvisory.wox +++ b/UI/Templates/SOGoACLEnglishRemovalAdvisory.wox @@ -13,10 +13,15 @@ has removed you from the access list for his '' folder. + You can unsubscribe directly to that folder by following this link: unsubscribe?mail-invitation=YES Otherwise, you will be able to unsubscribe later from the SOGo web interface. + +You can also no longer access this resource using the following URL: + + diff --git a/UI/Templates/SOGoACLFrenchAdditionAdvisory.wox b/UI/Templates/SOGoACLFrenchAdditionAdvisory.wox index 4686ccf7..3166ecc2 100644 --- a/UI/Templates/SOGoACLFrenchAdditionAdvisory.wox +++ b/UI/Templates/SOGoACLFrenchAdditionAdvisory.wox @@ -8,15 +8,20 @@ xmlns:label="OGo:label"> - vous a ajouté + a modifié les droits - vous a ajouté a sa liste de permission pour son dossier ''. -Vous pouvez vous inscrire directement a ce dossier en cliquant sur le lien suivant: + vous a ajouté à sa liste de permission pour son dossier ''. + +Vous pouvez vous inscrire directement à ce dossier en cliquant sur le lien suivant: unsubscribe?mail-invitation=YES Autrement, il vous sera toujours possible de vous inscrire plus tard via l'interface web de SOGo. + +De plus, vous pouvez aussi accéder au dossier en utilisant le lien suivant: + + diff --git a/UI/Templates/SOGoACLFrenchRemovalAdvisory.wox b/UI/Templates/SOGoACLFrenchRemovalAdvisory.wox index 7679866a..497a54ea 100644 --- a/UI/Templates/SOGoACLFrenchRemovalAdvisory.wox +++ b/UI/Templates/SOGoACLFrenchRemovalAdvisory.wox @@ -13,10 +13,15 @@ vous a enlevé de sa liste de permission pour son dossier ''. + Vous pouvez vous désinscrire directement en cliquant sur le lien suivant: unsubscribe?mail-invitation=YES Autrement, il vous sera toujours possible de vous désinscrire plus tard via l'interface web de SOGo. + +De plus, vous ne pouvez plus accéder au dossier en utilisant le lien suivant: + + diff --git a/UI/Templates/SOGoACLGermanAdditionAdvisory.wox b/UI/Templates/SOGoACLGermanAdditionAdvisory.wox index 313f9a44..d53f17b8 100644 --- a/UI/Templates/SOGoACLGermanAdditionAdvisory.wox +++ b/UI/Templates/SOGoACLGermanAdditionAdvisory.wox @@ -13,6 +13,7 @@ hat Ihnen den Zugang zu seinem Ordner '' erlaubt. + Folgende URL erlaubt Ihnen, sich sofort an diesem Ordner zu abonnieren: unsubscribe?mail-invitation=YES diff --git a/UI/Templates/SOGoACLGermanRemovalAdvisory.wox b/UI/Templates/SOGoACLGermanRemovalAdvisory.wox index 9638f657..9e02af19 100644 --- a/UI/Templates/SOGoACLGermanRemovalAdvisory.wox +++ b/UI/Templates/SOGoACLGermanRemovalAdvisory.wox @@ -13,6 +13,7 @@ erlaubt Ihnen nicht mehr den Zugang zu seinem Order ''. + Sie können sich sofort an folgender URL von diesem Order des-abonnieren: unsubscribe?mail-invitation=YES diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index 8b449799..087d4f84 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -310,20 +310,20 @@ function onMenuEditContact(event) { } function onMenuWriteToContact(event) { - var contactId = document.menuTarget.getAttribute('id'); - var contactRow = $(contactId); - var emailCell = contactRow.down('td', 1); + var contactId = document.menuTarget.getAttribute('id'); + var contactRow = $(contactId); + var emailCell = contactRow.down('td', 1); - if (!emailCell.firstChild) { // .nodeValue is the contact email address - window.alert(labels["The selected contact has no email address."]); - return false; - } + if (!emailCell.firstChild) { // .nodeValue is the contact email address + window.alert(labels["The selected contact has no email address."]); + return false; + } - openMailComposeWindow(ApplicationBaseURL + currentContactFolder - + "/" + contactId + "/write"); + openMailComposeWindow(ApplicationBaseURL + currentContactFolder + + "/" + contactId + "/write"); - if (document.body.hasClassName("popup")) - window.close(); + if (document.body.hasClassName("popup")) + window.close(); } function onMenuDeleteContact(event) { @@ -334,6 +334,11 @@ function onToolbarEditSelectedContacts(event) { var contactsList = $('contactsList'); var rows = contactsList.getSelectedRowsId(); + if (rows.length == 0) { + window.alert(labels["Please select a contact."]); + return false; + } + for (var i = 0; i < rows.length; i++) { openContactWindow(URLForFolderID(currentContactFolder) + "/" + rows[i] + "/edit", rows[i]); @@ -347,8 +352,10 @@ function onToolbarWriteToSelectedContacts(event) { var rows = contactsList.getSelectedRowsId(); var rowsWithEmail = 0; - if (rows.length == 0) + if (rows.length == 0) { + window.alert(labels["Please select a contact."]); return false; + } for (var i = 0; i < rows.length; i++) { var emailCell = $(rows[i]).down('td', 1); @@ -373,6 +380,11 @@ function uixDeleteSelectedContacts(sender) { var contactsList = $('contactsList'); var rows = contactsList.getSelectedRowsId(); + if (rows.length == 0) { + window.alert(labels["Please select a contact."]); + return false; + } + var contactView = $('contactView'); contactView.innerHTML = ''; diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 79be003e..da4c678d 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -81,12 +81,19 @@ function editEvent() { if (listOfSelection) { var nodes = listOfSelection.getSelectedRows(); + if (nodes.length == 0) { + window.alert(labels["Please select an event or a task."]); + return false; + } + for (var i = 0; i < nodes.length; i++) _editEventId(nodes[i].getAttribute("id"), nodes[i].calendar); } else if (selectedCalendarCell) { - _editEventId(selectedCalendarCell[0].cname, - selectedCalendarCell[0].calendar); + _editEventId(selectedCalendarCell[0].cname, + selectedCalendarCell[0].calendar); + } else { + window.alert(labels["Please select an event or a task."]); } return false; /* stop following the link */ @@ -135,6 +142,8 @@ function deleteEvent() { } _batchDeleteEvents(); } + } else { + window.alert(labels["Please select an event or a task."]); } } else if (selectedCalendarCell) { @@ -150,7 +159,7 @@ function deleteEvent() { } } else - window.alert("no selection"); + window.alert(labels["Please select an event or a task."]); return false; } diff --git a/UI/WebServerResources/UIxMailToSelection.js b/UI/WebServerResources/UIxMailToSelection.js index 4593693d..4a30bdf5 100644 --- a/UI/WebServerResources/UIxMailToSelection.js +++ b/UI/WebServerResources/UIxMailToSelection.js @@ -98,16 +98,15 @@ function fancyAddRow(shouldEdit, text) { lastChild = $("lastRow"); currentIndex++; - - proto = $('row_' + lastIndex); + proto = lastChild.previous("tr"); row = proto.cloneNode(true); - row.id = 'row_' + currentIndex; + row.setAttribute("id", 'row_' + currentIndex); // select popup var rowNodes = row.childNodesWithTag("td"); select = $(rowNodes[0]).childNodesWithTag("select")[0]; select.name = 'popup_' + currentIndex; -// select.value = row.childNodesWithTag("span")[0].childNodesWithTag("select")[0].value; + select.value = proto.down("select").value; input = $(rowNodes[1]).childNodesWithTag("input")[0]; input.name = 'addr_' + currentIndex; input.id = 'addr_' + currentIndex; @@ -136,7 +135,7 @@ function addressFieldGotFocus(sender) { function addressFieldLostFocus(sender) { lastIndex = this.getIndexFromIdentifier(sender.id); - + return false; } @@ -147,7 +146,7 @@ function removeLastEditedRowIfEmpty() { if (idx == 0) return; addr = $('addr_' + idx); if (!addr) return; - if (addr.value != '') return; + if (addr.value.strip() != '') return; addr = this.findAddressWithIndex(idx); if(addr) { var addresses = $('addr_addresses'); @@ -223,5 +222,8 @@ function hasRecipients() { return (count > 0) } -/* addressbook helpers */ +function initMailToSelection() { + currentIndex = lastIndex = $$("table#addressList tr").length - 2; +} +FastInit.addOnLoad(initMailToSelection); -- 2.39.5