diff --git a/ChangeLog b/ChangeLog index 14f27a35a..94fba7737 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,71 @@ +2012-01-04 Wolfgang Sourdeau + + * UI/WebServerResources/ContactsUI.js + (onAddressBooksMenuPrepareVisibility): the "new list", "sharing" and "import" + options are now greyed out properly, depending on the object type + and the new attributes below. + + * UI/Contacts/UIxContactFoldersView.m + (-currentContactFolderAclEditing) + (-currentContactFolderListEditing): new attribute accessors. + + * SoObjects/SOGo/SOGoFolder.m (-sendFolderAdvisoryTemplate:): + moved method from SOGoGCSFolder in order to make it available to + other folder classes. + + * SoObjects/SOGo/LDAPSource.m (-initFromUDSource:inDomain:): use + the new setters for certain ivars + take the new "abOU" key into + account. + (-setListRequiresDot:, -listRequiresDot:, -setSourceID:) + (-setDisplayName, -displayName, -setModifiers:): new accessors. + (-_convertRecordToLDAPAttributes): we now strip object classes that + are not supported by the server prior to remove the related fields. + (-hasUserAddressBooks): new method that returns whether user + addressbooks are supported, i.e. when "abOU" is set. + (-addressBookSourcesForUser:): when "abOU" is set, returns an + array of LDAPSource instances representing the personal + addressbooks of the specified user. + (-addAddressBookSource:withDisplayName:forUser:) + (-renameAddressBookSource:withDisplayName:forUser:) + (-removeAddressBookSource:forUser:): new methods with a + self-explicit name. + + * SoObjects/Contacts/SOGoContactSourceFolder.m + (-setIsPersonalSource, -isPersonalSource): new accessors for the + "isPersonalSource ivar". + (-lookupName:inContext:acquire:): setup the object classes of the + new entries to "inetorgperson" and "mozillaabpersonalpha". + (-lookupContactsWithFilter:onCriteria:sortBy:ordering:): check + whether "listRequiresDot" is set on the current source and return + the full listing if not required. + (-compare:): enhanced to treat personal sources as if they were + regular GCS folders, in order to sort them properly. + (-delete, -renameTo:): implemented method, required for the + corresponding web methods. + (-ownerInContext:) adapted method to personal sources. + + * SoObjects/Contacts/SOGoContactFolders.m (-appendPersonalSource): + overriden method for returning LDAP-based user addresbook sources. + (-newFolderWithName:andNameInContainer:): idem + (-renameLDAPAddressBook:withDisplayName:): new method that enables + the renaming of LDAP-based user addresbook sources. + (-removeLDAPAddressBook:): new method that enables + the removal of LDAP-based user addresbook sources. + +2012-01-03 Wolfgang Sourdeau + + * SoObjects/SOGo/SOGoParentFolder.m (-appendPersonalSources): made + method public so that it can be easily overriden in subclasses. + + * SoObjects/SOGo/SOGoUser.m (-authenticationSource): new method + that returned the SOGoSource instance that successfully recognized + the user represented by the current instance. + + * SoObjects/SOGo/SOGoUserManager.m + (_fillContactInfosForUser:withUIDorEmail:inDomain:): we now set + the identifier of the source that authenticated the specified user + as the "SOGoSource" entry of the returned dictionary. + 2012-01-02 Francis Lachapelle * UI/WebServerResources/UIxPreferences.js (getFilterFromEditor): diff --git a/SoObjects/Contacts/SOGoContactFolders.h b/SoObjects/Contacts/SOGoContactFolders.h index 169046b67..820efb92d 100644 --- a/SoObjects/Contacts/SOGoContactFolders.h +++ b/SoObjects/Contacts/SOGoContactFolders.h @@ -27,6 +27,10 @@ @interface SOGoContactFolders : SOGoParentFolder +- (NSException *) renameLDAPAddressBook: (NSString *) sourceID + withDisplayName: (NSString *) newDisplayName; +- (NSException *) removeLDAPAddressBook: (NSString *) sourceID; + @end #endif /* SOGOCONTACTFOLDERS_H */ diff --git a/SoObjects/Contacts/SOGoContactFolders.m b/SoObjects/Contacts/SOGoContactFolders.m index f0f3574f3..cb00f7387 100644 --- a/SoObjects/Contacts/SOGoContactFolders.m +++ b/SoObjects/Contacts/SOGoContactFolders.m @@ -33,10 +33,12 @@ #import #import +#import #import #import #import +#import #import #import #import @@ -45,6 +47,7 @@ #import "SOGoContactGCSFolder.h" #import "SOGoContactSourceFolder.h" + #import "SOGoContactFolders.h" #define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav" @@ -60,6 +63,62 @@ return [SOGoContactGCSFolder class]; } +- (void) _fetchLDAPAddressBooks: (id ) source +{ + id abSource; + SOGoContactSourceFolder *folder; + NSArray *abSources; + NSUInteger count, max; + NSString *name; + + abSources = [source addressBookSourcesForUser: owner]; + max = [abSources count]; + for (count = 0; count < max; count++) + { + abSource = [abSources objectAtIndex: count]; + name = [abSource sourceID]; + folder = [SOGoContactSourceFolder folderWithName: name + andDisplayName: [abSource displayName] + inContainer: self]; + [folder setSource: abSource]; + [folder setIsPersonalSource: YES]; + [subFolders setObject: folder forKey: name]; + } +} + +- (NSException *) appendPersonalSources +{ + SOGoUser *currentUser; + NSException *result; + id source; + + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + if ([source hasUserAddressBooks]) + { + result = nil; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + { + [self _fetchLDAPAddressBooks: source]; + if (![subFolders objectForKey: @"personal"]) + { + result = [source addAddressBookSource: @"personal" + withDisplayName: [self defaultFolderName] + forUser: owner]; + if (!result) + [self _fetchLDAPAddressBooks: source]; + } + } + } + else + result = [super appendPersonalSources]; + + return result; +} + - (NSException *) appendSystemSources { SOGoUserManager *um; @@ -101,6 +160,88 @@ return nil; } + +- (NSException *) newFolderWithName: (NSString *) name + andNameInContainer: (NSString *) newNameInContainer +{ + SOGoUser *currentUser; + NSException *result; + id source; + + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + if ([source hasUserAddressBooks]) + { + result = nil; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + { + result = [source addAddressBookSource: newNameInContainer + withDisplayName: name + forUser: owner]; + if (!result) + [self _fetchLDAPAddressBooks: source]; + } + } + else + result = [super newFolderWithName: name + andNameInContainer: newNameInContainer]; + + return result; +} + +- (NSException *) renameLDAPAddressBook: (NSString *) sourceID + withDisplayName: (NSString *) newDisplayName +{ + NSException *result; + SOGoUser *currentUser; + id source; + + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + result = [source renameAddressBookSource: sourceID + withDisplayName: newDisplayName + forUser: owner]; + else + result = [NSException exceptionWithHTTPStatus: 403 + reason: @"operation denied"]; + + return result; +} + +- (NSException *) removeLDAPAddressBook: (NSString *) sourceID +{ + NSException *result; + SOGoUser *currentUser; + id source; + + if ([sourceID isEqualToString: @"personal"]) + result = [NSException exceptionWithHTTPStatus: 403 + reason: @"folder 'personal' cannot be deleted"]; + else + { + result = nil; + currentUser = [context activeUser]; + source = [currentUser authenticationSource]; + /* We don't handle ACLs for user LDAP addressbooks yet, therefore only + the owner has access to his addressbooks. */ + if (activeUserIsOwner + || [[currentUser login] isEqualToString: owner]) + result = [source removeAddressBookSource: sourceID + forUser: owner]; + else + result = [NSException exceptionWithHTTPStatus: 403 + reason: @"operation denied"]; + } + + return result; +} - (NSString *) defaultFolderName { diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.h b/SoObjects/Contacts/SOGoContactSourceFolder.h index 09effd47d..d37b1c631 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.h +++ b/SoObjects/Contacts/SOGoContactSourceFolder.h @@ -35,6 +35,7 @@ { id source; NSMutableDictionary *childRecords; + BOOL isPersonalSource; } + (id) folderWithName: (NSString *) aName @@ -48,6 +49,9 @@ - (NSException *) saveLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry; - (NSException *) deleteLDIFEntry: (SOGoContactLDIFEntry *) ldifEntry; +- (void) setIsPersonalSource: (BOOL) isPersonal; +- (BOOL) isPersonalSource; + @end diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m b/SoObjects/Contacts/SOGoContactSourceFolder.m index ba576aa8f..2b7c942da 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m @@ -37,12 +37,16 @@ #import #import -#import -#import #import #import #import +#import +#import +#import +#import +#import "SOGoContactFolders.h" +#import "SOGoContactGCSFolder.h" #import "SOGoContactLDIFEntry.h" #import "SOGoContactSourceFolder.h" @@ -107,6 +111,16 @@ return source; } +- (void) setIsPersonalSource: (BOOL) isPersonal +{ + isPersonalSource = isPersonal; +} + +- (BOOL) isPersonalSource +{ + return isPersonalSource; +} + - (NSString *) groupDavResourceType { return @"vcard-collection"; @@ -135,6 +149,7 @@ SOGoContactLDIFEntry *obj; NSString *url; BOOL isNew = NO; + NSArray *baseClasses; /* first check attributes directly bound to the application */ obj = [super lookupName: objectName inContext: lookupContext acquire: NO]; @@ -152,7 +167,11 @@ url = [[[lookupContext request] uri] urlWithoutParameters]; if ([url hasSuffix: @"AsContact"]) { - ldifEntry = [NSMutableDictionary dictionary]; + baseClasses = [NSArray arrayWithObjects: @"inetorgperson", + @"mozillaabpersonalpha", nil]; + ldifEntry = [NSMutableDictionary + dictionaryWithObject: baseClasses + forKey: @"objectclass"]; isNew = YES; } } @@ -294,7 +313,8 @@ result = nil; - if ([filter length] > 0 && [criteria isEqualToString: @"name_or_address"]) + if (([filter length] > 0 && [criteria isEqualToString: @"name_or_address"]) + || ![source listRequiresDot]) { records = [source fetchContactsMatching: filter]; [childRecords setObjects: records @@ -335,22 +355,88 @@ - (NSComparisonResult) compare: (id) otherFolder { NSComparisonResult comparison; + BOOL otherIsPersonal; - if ([NSStringFromClass([otherFolder class]) - isEqualToString: @"SOGoContactGCSFolder"]) - comparison = NSOrderedDescending; + otherIsPersonal = ([otherFolder isKindOfClass: [SOGoContactGCSFolder class]] + || ([otherFolder isKindOfClass: isa] && [otherFolder isPersonalSource])); + + if (isPersonalSource) + { + if (otherIsPersonal && ![nameInContainer isEqualToString: @"personal"]) + { + if ([[otherFolder nameInContainer] isEqualToString: @"personal"]) + comparison = NSOrderedDescending; + else + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + } + else + comparison = NSOrderedAscending; + } else - comparison - = [[self displayName] - localizedCaseInsensitiveCompare: [otherFolder displayName]]; + { + if (otherIsPersonal) + comparison = NSOrderedDescending; + else + comparison + = [[self displayName] + localizedCaseInsensitiveCompare: [otherFolder displayName]]; + } return comparison; } +/* common methods */ + +- (NSException *) delete +{ + NSException *error; + + if (isPersonalSource) + { + error = [(SOGoContactFolders *) container + removeLDAPAddressBook: nameInContainer]; + if (!error && [[context request] handledByDefaultHandler]) + [self sendFolderAdvisoryTemplate: @"Removal"]; + } + else + error = [NSException exceptionWithHTTPStatus: 501 /* not implemented */ + reason: @"delete not available on system sources"]; + + return error; +} + +- (void) renameTo: (NSString *) newName +{ + NSException *error; + + if (isPersonalSource) + { + if (![[source displayName] isEqualToString: newName]) + { + error = [(SOGoContactFolders *) container + renameLDAPAddressBook: nameInContainer + withDisplayName: newName]; + if (!error) + [self setDisplayName: newName]; + } + } + /* If public source then method is ignored, maybe we should return an + NSException instead... */ +} + /* acls */ - (NSString *) ownerInContext: (WOContext *) noContext { - return @"nobody"; + NSString *sourceOwner; + + if (isPersonalSource) + sourceOwner = [[source modifiers] objectAtIndex: 0]; + else + sourceOwner = @"nobody"; + + return sourceOwner; } - (NSArray *) subscriptionRoles diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 64fd9c02d..821dfec5d 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -31,10 +31,11 @@ #include "SOGoConstants.h" @class LDAPSourceSchema; -@class NSDictionary; -@class NSString; -@class NGLdapConnection; @class NGLdapEntry; +@class NSException; +@class NSMutableArray; +@class NSMutableDictionary; +@class NSString; @interface LDAPSource : NSObject { @@ -42,6 +43,8 @@ int queryTimeout; NSString *sourceID; + NSString *displayName; + NSString *bindDN; // The bindDN/password could be either the source's one NSString *password; // or the current user if _bindAsCurrentUser is set to YES NSString *sourceBindDN; // while sourceBindDN/sourceBindPassword always belong to the source @@ -50,6 +53,7 @@ unsigned int port; NSString *encryption; NSString *_filter; + BOOL _bindAsCurrentUser; NSString *_scope; NSString *baseDN; @@ -60,7 +64,8 @@ NSArray *mailFields, *searchFields; NSString *IMAPHostField, *IMAPLoginField; NSArray *bindFields; - BOOL _bindAsCurrentUser; + + BOOL listRequiresDot; NSString *domain; NSString *contactInfoAttribute; @@ -80,6 +85,9 @@ NSString *kindField; NSString *multipleBookingsField; + /* user addressbooks */ + NSString *abOU; + /* ACL */ NSArray *modifiers; } diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index 27ee22fe8..a36eeb48c 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -26,6 +26,7 @@ #import #import +#import #import #import @@ -42,7 +43,6 @@ #import "SOGoSystemDefaults.h" #import "LDAPSource.h" - #import "../../Main/SOGo.h" static Class NSStringK; @@ -139,6 +139,9 @@ static Class NSStringK; { if ((self = [super init])) { + sourceID = nil; + displayName = nil; + bindDN = nil; password = nil; sourceBindDN = nil; @@ -146,7 +149,6 @@ static Class NSStringK; hostname = nil; port = 389; encryption = nil; - sourceID = nil; domain = nil; baseDN = nil; @@ -164,6 +166,7 @@ static Class NSStringK; bindFields = nil; _scope = @"sub"; _filter = nil; + listRequiresDot = YES; searchAttributes = nil; passwordPolicy = NO; @@ -220,11 +223,12 @@ static Class NSStringK; inDomain: (NSString *) sourceDomain { SOGoDomainDefaults *dd; - NSNumber *udQueryLimit, *udQueryTimeout; + NSNumber *udQueryLimit, *udQueryTimeout, *dotValue; if ((self = [self init])) { - ASSIGN(sourceID, [udSource objectForKey: @"id"]); + [self setSourceID: [udSource objectForKey: @"id"]]; + [self setDisplayName: [udSource objectForKey: @"displayName"]]; [self setBindDN: [udSource objectForKey: @"bindDN"] password: [udSource objectForKey: @"bindPassword"] @@ -245,9 +249,15 @@ static Class NSStringK; kindField: [udSource objectForKey: @"KindFieldName"] andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]]; + dotValue = [udSource objectForKey: @"listRequiresDot"]; + if (dotValue) + [self setListRequiresDot: [dotValue boolValue]]; [self setContactMapping: [udSource objectForKey: @"mapping"] andObjectClasses: [udSource objectForKey: @"objectClasses"]]; + [self setModifiers: [udSource objectForKey: @"modifiers"]]; + ASSIGN (abOU, [udSource objectForKey: @"abOU"]); + if ([sourceDomain length]) { dd = [SOGoDomainDefaults defaultsForDomain: sourceDomain]; @@ -283,10 +293,8 @@ static Class NSStringK; if ([udSource objectForKey: @"passwordPolicy"]) passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue]; - - ASSIGN (modifiers, [udSource objectForKey: @"modifiers"]); } - + return self; } @@ -386,6 +394,16 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField ASSIGN(multipleBookingsField, [newMultipleBookingsField lowercaseString]); } +- (void) setListRequiresDot: (BOOL) aBool +{ + listRequiresDot = aBool; +} + +- (BOOL) listRequiresDot +{ + return listRequiresDot; +} + - (void) setContactMapping: (NSDictionary *) newMapping andObjectClasses: (NSArray *) newObjectClasses { @@ -438,6 +456,11 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField [ldapConnection setQuerySizeLimit: queryLimit]; if (queryTimeout > 0) [ldapConnection setQueryTimeLimit: queryTimeout]; + if (!schema) + { + schema = [LDAPSourceSchema new]; + [schema readSchemaFromConnection: ldapConnection]; + } } else ldapConnection = nil; @@ -691,11 +714,18 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField [qs appendString: searchFormat]; } - if (_filter && [_filter length]) + if ([_filter length]) [qs appendFormat: @" AND %@", _filter]; qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; } + else if (!listRequiresDot) + { + qs = [NSMutableString stringWithFormat: @"(%@='*')", CNField]; + if ([_filter length]) + [qs appendFormat: @" AND %@", _filter]; + qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; + } else qualifier = nil; @@ -1058,7 +1088,7 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField contacts = [NSMutableArray array]; - if ([match length] > 0) + if ([match length] > 0 || !listRequiresDot) { ldapConnection = [self _ldapConnection]; qualifier = [self _qualifierForFilter: match]; @@ -1093,11 +1123,6 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField // attributes = [self _searchAttributes]; ldapConnection = [self _ldapConnection]; - if (!schema) - { - schema = [LDAPSourceSchema new]; - [schema readSchemaFromConnection: ldapConnection]; - } attributes = [NSArray arrayWithObject: @"*"]; if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) @@ -1221,16 +1246,36 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return ldapEntry; } +- (void) setSourceID: (NSString *) newSourceID +{ + ASSIGN (sourceID, newSourceID); +} + - (NSString *) sourceID { return sourceID; } +- (void) setDisplayName: (NSString *) newDisplayName +{ + ASSIGN (displayName, newDisplayName); +} + +- (NSString *) displayName +{ + return displayName; +} + - (NSString *) baseDN { return baseDN; } +- (void) setModifiers: (NSArray *) newModifiers +{ + ASSIGN (modifiers, newModifiers); +} + - (NSArray *) modifiers { return modifiers; @@ -1240,33 +1285,50 @@ static NSArray * _convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifRecord) { /* convert resulting record to NGLdapEntry: + - strip non-existing object classes - ignore fields with empty values - ignore extra fields - use correct case for LDAP attribute matching classes */ - NSMutableArray *attributes; + NSMutableArray *validClasses, *validFields, *attributes; NGLdapAttribute *attribute; NSArray *classes, *fields, *values; - NSString *field, *lowerField, *value; + NSString *objectClass, *field, *lowerField, *value; NSUInteger count, max, valueCount, valueMax; - attributes = [NSMutableArray new]; - classes = [ldifRecord objectForKey: @"objectclass"]; if ([classes isKindOfClass: NSStringK]) classes = [NSArray arrayWithObject: classes]; - fields = [schema fieldsForClasses: classes]; + max = [classes count]; + validClasses = [NSMutableArray array]; + validFields = [NSMutableArray array]; + for (count = 0; count < max; count++) + { + objectClass = [classes objectAtIndex: count]; + fields = [schema fieldsForClass: objectClass]; + if ([fields count] > 0) + { + [validClasses addObject: objectClass]; + [validFields addObjectsFromArray: fields]; + } + } - max = [fields count]; + attributes = [NSMutableArray new]; + max = [validFields count]; for (count = 0; count < max; count++) { attribute = nil; - field = [fields objectAtIndex: count]; + field = [validFields objectAtIndex: count]; lowerField = [field lowercaseString]; if (![lowerField isEqualToString: @"dn"]) { - values = [ldifRecord objectForKey: lowerField]; - if ([values isKindOfClass: NSStringK]) - values = [NSArray arrayWithObject: values]; + if ([lowerField isEqualToString: @"objectclass"]) + values = validClasses; + else + { + values = [ldifRecord objectForKey: lowerField]; + if ([values isKindOfClass: NSStringK]) + values = [NSArray arrayWithObject: values]; + } valueMax = [values count]; for (valueCount = 0; valueCount < valueMax; valueCount++) { @@ -1292,7 +1354,7 @@ _convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifReco - (NSException *) addContactEntry: (NSDictionary *) roLdifRecord withID: (NSString *) aId { - NSException *result = nil; + NSException *result; NGLdapEntry *newEntry; NSMutableDictionary *ldifRecord; NSArray *attributes; @@ -1302,12 +1364,6 @@ _convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifReco if ([aId length] > 0) { ldapConnection = [self _ldapConnection]; - if (!schema) - { - schema = [LDAPSourceSchema new]; - [schema readSchemaFromConnection: ldapConnection]; - } - ldifRecord = [roLdifRecord mutableCopy]; [ldifRecord autorelease]; [ldifRecord setObject: aId forKey: UIDField]; @@ -1336,6 +1392,7 @@ _convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifReco NS_DURING { [ldapConnection addEntry: newEntry]; + result = nil; } NS_HANDLER { @@ -1413,7 +1470,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection, - (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord { - NSException *result = nil; + NSException *result; NSString *dn; NSMutableDictionary *ldifRecord; NSArray *attributes, *changes; @@ -1423,12 +1480,6 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection, if ([dn length] > 0) { ldapConnection = [self _ldapConnection]; - if (!schema) - { - schema = [LDAPSourceSchema new]; - [schema readSchemaFromConnection: ldapConnection]; - } - ldifRecord = [roLdifRecord mutableCopy]; [ldifRecord autorelease]; [self _applyContactMappingToOutput: ldifRecord]; @@ -1440,6 +1491,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection, { [ldapConnection modifyEntryWithDN: dn changes: changes]; + result = nil; } NS_HANDLER { @@ -1455,7 +1507,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection, - (NSException *) removeContactEntryWithID: (NSString *) aId { - NSException *result = nil; + NSException *result; NGLdapConnection *ldapConnection; NSString *dn; @@ -1464,6 +1516,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection, NS_DURING { [ldapConnection removeEntryWithDN: dn]; + result = nil; } NS_HANDLER { @@ -1474,4 +1527,233 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection, return result; } +/* user addressbooks */ +- (BOOL) hasUserAddressBooks +{ + return ([abOU length] > 0); +} + +- (NSArray *) addressBookSourcesForUser: (NSString *) user +{ + NSMutableArray *sources; + NSString *abBaseDN; + NGLdapConnection *ldapConnection; + NSArray *attributes, *modifier; + NSEnumerator *entries; + NGLdapEntry *entry; + NSMutableDictionary *entryRecord; + NSDictionary *sourceRec; + LDAPSource *ab; + + if ([self hasUserAddressBooks]) + { + /* list subentries */ + sources = [NSMutableArray array]; + + ldapConnection = [self _ldapConnection]; + abBaseDN = [NSString stringWithFormat: @"ou=%@,%@=%@,%@", abOU, IDField, user, baseDN]; + + /* test ou=addressbooks entry */ + attributes = [NSArray arrayWithObject: @"*"]; + entries = [ldapConnection baseSearchAtBaseDN: abBaseDN + qualifier: nil + attributes: attributes]; + entry = [entries nextObject]; + if (entry) + { + attributes = [NSArray arrayWithObjects: @"ou", @"description", nil]; + entries = [ldapConnection flatSearchAtBaseDN: abBaseDN + qualifier: nil + attributes: attributes]; + modifier = [NSArray arrayWithObject: user]; + while ((entry = [entries nextObject])) + { + sourceRec = [entry _asDictionary]; + ab = [LDAPSource new]; + [ab setSourceID: [sourceRec objectForKey: @"ou"]]; + [ab setDisplayName: [sourceRec objectForKey: @"description"]]; + [ab setBindDN: bindDN + password: password + hostname: hostname + port: [NSString stringWithFormat: @"%d", port] + encryption: encryption + bindAsCurrentUser: NO]; + [ab setBaseDN: [entry dn] + IDField: @"cn" + CNField: @"displayName" + UIDField: @"cn" + mailFields: nil + searchFields: nil + IMAPHostField: nil + IMAPLoginField: nil + bindFields: nil + kindField: nil + andMultipleBookingsField: nil]; + [ab setListRequiresDot: NO]; + [ab setModifiers: modifier]; + [sources addObject: ab]; + [ab release]; + } + } + else + { + entryRecord = [NSMutableDictionary dictionary]; + [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; + [entryRecord setObject: @"addressbooks" forKey: @"ou"]; + attributes = _convertRecordToLDAPAttributes (schema, entryRecord); + entry = [[NGLdapEntry alloc] initWithDN: abBaseDN + attributes: attributes]; + [entry autorelease]; + [attributes release]; + NS_DURING + { + [ldapConnection addEntry: entry]; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to create ou=addressbooks" + @" entry for user"]; + } + NS_ENDHANDLER; + } + } + else + sources = nil; + + return sources; +} + +- (NSException *) addAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSException *result; + NSString *abDN; + NGLdapConnection *ldapConnection; + NSArray *attributes; + NGLdapEntry *entry; + NSMutableDictionary *entryRecord; + + if ([self hasUserAddressBooks]) + { + abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", + newId, abOU, IDField, user, baseDN]; + entryRecord = [NSMutableDictionary dictionary]; + [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; + [entryRecord setObject: newId forKey: @"ou"]; + if ([newDisplayName length] > 0) + [entryRecord setObject: newDisplayName forKey: @"description"]; + ldapConnection = [self _ldapConnection]; + attributes = _convertRecordToLDAPAttributes (schema, entryRecord); + entry = [[NGLdapEntry alloc] initWithDN: abDN + attributes: attributes]; + [entry autorelease]; + [attributes release]; + NS_DURING + { + [ldapConnection addEntry: entry]; + result = nil; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to create addressbook entry"]; + result = localException; + } + NS_ENDHANDLER; + } + else + result = [NSException exceptionWithName: @"LDAPSourceIOException" + reason: @"user addressbooks" + @" are not supported" + userInfo: nil]; + + return result; +} + +- (NSException *) renameAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSException *result; + NSString *abDN; + NGLdapConnection *ldapConnection; + NSArray *attributes, *changes; + NSMutableDictionary *entryRecord; + + if ([self hasUserAddressBooks]) + { + abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", + newId, abOU, IDField, user, baseDN]; + entryRecord = [NSMutableDictionary dictionary]; + [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"]; + [entryRecord setObject: newId forKey: @"ou"]; + if ([newDisplayName length] > 0) + [entryRecord setObject: newDisplayName forKey: @"description"]; + ldapConnection = [self _ldapConnection]; + attributes = _convertRecordToLDAPAttributes (schema, entryRecord); + changes = _makeLDAPChanges (ldapConnection, abDN, attributes); + [attributes release]; + NS_DURING + { + [ldapConnection modifyEntryWithDN: abDN + changes: changes]; + result = nil; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to rename addressbook entry"]; + result = localException; + } + NS_ENDHANDLER; + } + else + result = [NSException exceptionWithName: @"LDAPSourceIOException" + reason: @"user addressbooks" + @" are not supported" + userInfo: nil]; + + return result; +} + +- (NSException *) removeAddressBookSource: (NSString *) newId + forUser: (NSString *) user +{ + NSException *result; + NSString *abDN; + NGLdapConnection *ldapConnection; + NSEnumerator *entries; + NGLdapEntry *entry; + + if ([self hasUserAddressBooks]) + { + abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@", + newId, abOU, IDField, user, baseDN]; + ldapConnection = [self _ldapConnection]; + NS_DURING + { + /* we must remove the ab sub=entries prior to the ab entry */ + entries = [ldapConnection flatSearchAtBaseDN: abDN + qualifier: nil + attributes: nil]; + while ((entry = [entries nextObject])) + [ldapConnection removeEntryWithDN: [entry dn]]; + [ldapConnection removeEntryWithDN: abDN]; + result = nil; + } + NS_HANDLER + { + [self errorWithFormat: @"failed to remove addressbook entry"]; + result = localException; + } + NS_ENDHANDLER; + } + else + result = [NSException exceptionWithName: @"LDAPSourceIOException" + reason: @"user addressbooks" + @" are not supported" + userInfo: nil]; + + return result; +} + @end diff --git a/SoObjects/SOGo/SOGoFolder.h b/SoObjects/SOGo/SOGoFolder.h index cc97cf153..d51076805 100644 --- a/SoObjects/SOGo/SOGoFolder.h +++ b/SoObjects/SOGo/SOGoFolder.h @@ -25,6 +25,8 @@ #import "SOGoObject.h" +@class NSString; + @interface SOGoFolder : SOGoObject { NSMutableString *displayName; @@ -55,6 +57,9 @@ /* outlook */ - (NSString *) outlookFolderClass; +/* email advisories */ +- (void) sendFolderAdvisoryTemplate: (NSString *) template; + @end @interface SOGoFolder (GroupDAVExtensions) diff --git a/SoObjects/SOGo/SOGoFolder.m b/SoObjects/SOGo/SOGoFolder.m index 5587f18b8..adb8e4c4c 100644 --- a/SoObjects/SOGo/SOGoFolder.m +++ b/SoObjects/SOGo/SOGoFolder.m @@ -27,6 +27,7 @@ #import #import +#import #import #import #import @@ -37,12 +38,16 @@ #import +#import + #import "DOMNode+SOGo.h" #import "NSArray+Utilities.h" #import "NSObject+DAV.h" #import "NSString+DAV.h" #import "NSString+Utilities.h" #import "SOGoPermissions.h" +#import "SOGoUser.h" +#import "SOGoDomainDefaults.h" #import "SOGoWebDAVAclManager.h" #import "WORequest+SOGo.h" #import "WOResponse+SOGo.h" @@ -248,6 +253,30 @@ return comparison; } +/* email advisories */ + +- (void) sendFolderAdvisoryTemplate: (NSString *) template +{ + NSString *pageName; + SOGoUser *user; + SOGoFolderAdvisory *page; + NSString *language; + + user = [SOGoUser userWithLogin: [self ownerInContext: context]]; + if ([[user domainDefaults] foldersSendEMailNotifications]) + { + language = [[user userDefaults] language]; + pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory", + language, template]; + + page = [[WOApplication application] pageWithName: pageName + inContext: context]; + [page setFolderObject: self]; + [page setRecipientUID: [user login]]; + [page send]; + } +} + /* WebDAV */ - (NSString *) davEntityTag diff --git a/SoObjects/SOGo/SOGoGCSFolder.h b/SoObjects/SOGo/SOGoGCSFolder.h index 69c641020..b4575b815 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.h +++ b/SoObjects/SOGo/SOGoGCSFolder.h @@ -118,9 +118,6 @@ - (void) removeAclsForUsers: (NSArray *) users forObjectAtPath: (NSArray *) objectPathArray; -/* advisories */ -- (void) sendFolderAdvisoryTemplate: (NSString *) template; - /* DAV */ - (NSURL *) publicDavURL; - (NSURL *) realDavURL; diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 4b3c85070..7ba81b5df 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -52,7 +52,6 @@ #import #import #import -#import #import "NSDictionary+Utilities.h" #import "NSArray+Utilities.h" @@ -500,28 +499,6 @@ static NSArray *childRecordFields = nil; return folder; } -- (void) sendFolderAdvisoryTemplate: (NSString *) template -{ - NSString *pageName; - SOGoUser *user; - SOGoFolderAdvisory *page; - NSString *language; - - user = [SOGoUser userWithLogin: [self ownerInContext: context]]; - if ([[user domainDefaults] foldersSendEMailNotifications]) - { - language = [[user userDefaults] language]; - pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory", - language, template]; - - page = [[WOApplication application] pageWithName: pageName - inContext: context]; - [page setFolderObject: self]; - [page setRecipientUID: [user login]]; - [page send]; - } -} - - (BOOL) create { NSException *result; diff --git a/SoObjects/SOGo/SOGoParentFolder.h b/SoObjects/SOGo/SOGoParentFolder.h index cfd272121..278d94ac6 100644 --- a/SoObjects/SOGo/SOGoParentFolder.h +++ b/SoObjects/SOGo/SOGoParentFolder.h @@ -42,6 +42,8 @@ - (NSString *) defaultFolderName; +- (NSException *) appendPersonalSources; + - (void) setBaseOCSPath: (NSString *) newOCSPath; - (NSArray *) toManyRelationshipKeys; diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index 64cf44218..08ecd35dd 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -183,7 +183,6 @@ static SoSecurityManager *sm = nil; SOGoGCSFolder *folder; NSString *key; NSException *error; - SOGoUser *currentUser; if (!subFolderClass) subFolderClass = [[self class] subFolderClass]; @@ -191,8 +190,6 @@ static SoSecurityManager *sm = nil; error = [fc evaluateExpressionX: sql]; if (!error) { - currentUser = [context activeUser]; - attrs = [fc describeResults: NO]; while ((row = [fc fetchAttributes: attrs withZone: NULL])) { diff --git a/SoObjects/SOGo/SOGoSource.h b/SoObjects/SOGo/SOGoSource.h index 3c8f90380..2af5bd14d 100644 --- a/SoObjects/SOGo/SOGoSource.h +++ b/SoObjects/SOGo/SOGoSource.h @@ -41,6 +41,10 @@ - (NSString *) domain; +/* requires a "." to obtain the full list of contacts */ +- (void) setListRequiresDot: (BOOL) aBool; +- (BOOL) listRequiresDot; + - (BOOL) checkLogin: (NSString *) _login password: (NSString *) _pwd perr: (SOGoPasswordPolicyError *) _perr @@ -57,14 +61,36 @@ - (NSArray *) allEntryIDs; - (NSArray *) fetchContactsMatching: (NSString *) filter; + +- (void) setSourceID: (NSString *) newSourceID; - (NSString *) sourceID; + +- (void) setDisplayName: (NSString *) newDisplayName; +- (NSString *) displayName; + +- (void) setModifiers: (NSArray *) newModifiers; - (NSArray *) modifiers; +- (BOOL) hasUserAddressBooks; +- (NSArray *) addressBookSourcesForUser: (NSString *) user; + - (NSException *) addContactEntry: (NSDictionary *) roLdifRecord withID: (NSString *) aId; - (NSException *) updateContactEntry: (NSDictionary *) ldifRecord; - (NSException *) removeContactEntryWithID: (NSString *) aId; +/* user address books */ +- (NSArray *) addressBookSourcesForUser: (NSString *) user; + +- (NSException *) addAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user; +- (NSException *) renameAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user; +- (NSException *) removeAddressBookSource: (NSString *) newId + forUser: (NSString *) user; + @end @protocol SOGoDNSource diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 7400021a9..7e7f0982e 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -52,6 +52,8 @@ @class SOGoUserProfile; @class SOGoUserSettings; +@protocol SOGoSource; + @interface SOGoUser : SoUser { SOGoUserDefaults *_defaults; @@ -88,6 +90,7 @@ /* properties */ - (NSString *) domain; +- (id ) authenticationSource; - (NSArray *) allEmails; - (BOOL) hasEmail: (NSString *) email; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 4d7aa6c67..ba4c0e471 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -287,6 +287,17 @@ return [self _fetchFieldForUser: @"c_domain"]; } +- (id ) authenticationSource +{ + NSString *sourceID; + SOGoUserManager *um; + + sourceID = [self _fetchFieldForUser: @"SOGoSource"]; + um = [SOGoUserManager sharedUserManager]; + + return [um sourceWithID: sourceID]; +} + - (NSArray *) allEmails { if (!allEmails) diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index 6d5e1415e..336585ba6 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -573,7 +573,7 @@ NSMutableArray *emails; NSDictionary *userEntry; NSEnumerator *sogoSources; - NSObject *currentSource; + NSObject *currentSource; NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname, *c_imaplogin; NSArray *c_emails; BOOL access; @@ -592,12 +592,14 @@ sogoSources = [[self authenticationSourceIDsInDomain: domain] objectEnumerator]; - while ((sourceID = [sogoSources nextObject])) + userEntry = nil; + while (!userEntry && (sourceID = [sogoSources nextObject])) { currentSource = [_sources objectForKey: sourceID]; userEntry = [currentSource lookupContactEntryWithUIDorEmail: uid]; if (userEntry) { + [currentUser setObject: sourceID forKey: @"SOGoSource"]; if (!cn) cn = [userEntry objectForKey: @"c_cn"]; if (!c_uid) diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m index 6950e2d7e..441808915 100644 --- a/SoObjects/SOGo/SQLSource.m +++ b/SoObjects/SOGo/SQLSource.m @@ -691,13 +691,46 @@ return results; } +- (void) setSourceID: (NSString *) newSourceID +{ +} + - (NSString *) sourceID { return _sourceID; } +- (void) setDisplayName: (NSString *) newDisplayName +{ +} + +- (NSString *) displayName +{ + /* This method is only used when supporting user "source" addressbooks, + which is only supported by the LDAP backend for now. */ + return _sourceID; +} + +- (void) setListRequiresDot: (BOOL) newListRequiresDot +{ +} + +- (BOOL) listRequiresDot +{ + /* This method is not implemented for SQLSource. It must enable a mechanism + where using "." is not required to list the content of addressbooks. */ + return YES; +} + +/* card editing */ +- (void) setModifiers: (NSArray *) newModifiers +{ +} + - (NSArray *) modifiers { + /* This method is only used when supporting card editing, + which is only supported by the LDAP backend for now. */ return nil; } @@ -741,4 +774,59 @@ userInfo: nil]; } +/* user addressbooks */ +- (BOOL) hasUserAddressBooks +{ + return NO; +} + +- (NSArray *) addressBookSourcesForUser: (NSString *) user +{ + return nil; +} + +- (NSException *) addAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) renameAddressBookSource: (NSString *) newId + withDisplayName: (NSString *) newDisplayName + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + +- (NSException *) removeAddressBookSource: (NSString *) newId + forUser: (NSString *) user +{ + NSString *reason; + + reason = [NSString stringWithFormat: @"method '%@' is not available" + @" for class '%@'", NSStringFromSelector (_cmd), + NSStringFromClass (isa)]; + + return [NSException exceptionWithName: @"SQLSourceIOException" + reason: reason + userInfo: nil]; +} + @end diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index a9d56ac3e..e520318a7 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -52,8 +52,16 @@ #import "UIxContactFoldersView.h" +Class SOGoContactSourceFolderK, SOGoGCSFolderK; + @implementation UIxContactFoldersView ++ (void) initialize +{ + SOGoContactSourceFolderK = [SOGoContactSourceFolder class]; + SOGoGCSFolderK = [SOGoGCSFolder class]; +} + - (id) init { if ((self = [super init])) @@ -110,11 +118,13 @@ folders = [self clientObject]; folder = [folders lookupPersonalFolder: @"personal" ignoringRights: YES]; - - contactInfos = [folder lookupContactsWithFilter: nil - onCriteria: nil - sortBy: @"c_cn" - ordering: NSOrderedAscending]; + if (folder && [folder conformsToProtocol: @protocol (SOGoContactFolder)]) + contactInfos = [folder lookupContactsWithFilter: nil + onCriteria: nil + sortBy: @"c_cn" + ordering: NSOrderedAscending]; + else + contactInfos = nil; return contactInfos; } @@ -183,7 +193,7 @@ { folder = [folders objectAtIndex: i]; /* We first search in LDAP folders (in case of duplicated entries in GCS folders) */ - if ([folder isKindOfClass: [SOGoContactSourceFolder class]]) + if ([folder isKindOfClass: SOGoContactSourceFolderK]) [sortedFolders insertObject: folder atIndex: 0]; else [sortedFolders addObject: folder]; @@ -287,10 +297,23 @@ - (NSString *) currentContactFolderClass { - return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]] + return (([currentFolder isKindOfClass: SOGoContactSourceFolderK] + && ![currentFolder isPersonalSource]) ? @"remote" : @"local"); } +- (NSString *) currentContactFolderAclEditing +{ + return ([currentFolder isKindOfClass: SOGoGCSFolderK] + ? @"available": @"unavailable"); +} + +- (NSString *) currentContactFolderListEditing +{ + return ([currentFolder isKindOfClass: SOGoGCSFolderK] + ? @"available": @"unavailable"); +} + - (NSString *) verticalDragHandleStyle { NSString *vertical; diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index ca66751fe..8aadf26a2 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -139,6 +139,11 @@ pageName = "UIxContactEditor"; actionName = "new"; }; + renameFolder = { + protectedBy = "Change Permissions"; + actionClass = "UIxFolderActions"; + actionName = "renameFolder"; + }; mailer-contacts = { protectedBy = ""; pageName = "UIxContactFoldersView"; diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index e8f304862..a8a9a9653 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -121,6 +121,8 @@ >
  • diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js index 89f070d94..8f1e2e656 100644 --- a/UI/WebServerResources/ContactsUI.js +++ b/UI/WebServerResources/ContactsUI.js @@ -258,17 +258,6 @@ function onContactContextMenuHide(event) { this.stopObserving("contextmenu:hide", onContactContextMenuHide); } -function onFolderMenuHide(event) { - var topNode = $('d'); - - if (topNode.menuSelectedEntry) { - topNode.menuSelectedEntry.deselect(); - topNode.menuSelectedEntry = null; - } - if (topNode.selectedEntry) - topNode.selectedEntry.selectElement(); -} - function _onContactMenuAction(folderItem, action, refresh) { var selectedFolders = $("contactFolders").getSelectedNodes(); var folderId = $(folderItem).readAttribute("folderId"); @@ -308,8 +297,9 @@ function onContactMenuMove(event) { function onMenuExportContact (event) { var selectedFolders = $("contactFolders").getSelectedNodes(); - var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); - if (selectedFolderId != "/shared") { + var canExport = (selectedFolders[0].getAttribute("owner") != "nobody"); + if (canExport) { + var selectedFolderId = $(selectedFolders[0]).readAttribute("id"); var contactIds = $(document.menuTarget).collect(function(row) { return row.getAttribute("id"); }); @@ -599,10 +589,11 @@ function newContact(sender) { function newList(sender) { var li = $(Contact.currentAddressBook); - if (li.hasClassName("remote")) - showAlertDialog(_("You cannot create a list in a shared address book.")); - else + var listEditing = li.getAttribute("list-editing"); + if (listEditing && listEditing == "available") openContactWindow(URLForFolderID(Contact.currentAddressBook) + "/newlist"); + else + showAlertDialog(_("You cannot create a list in a shared address book.")); return false; } @@ -1069,15 +1060,15 @@ function onMenuSharing(event) { var folders = $("contactFolders"); var selected = folders.getSelectedNodes()[0]; - var owner = selected.getAttribute("owner"); - if (owner == "nobody") - showAlertDialog(_("The user rights cannot be edited for this object!")); - else { + var aclEditing = selected.getAttribute("acl-editing"); + if (aclEditing && aclEditing == "available") { var title = this.innerHTML; var url = URLForFolderID(selected.getAttribute("id")); openAclWindow(url + "/acls", title); } + else + showAlertDialog(_("The user rights cannot be edited for this object!")); } function onAddressBooksMenuPrepareVisibility() { @@ -1090,30 +1081,55 @@ function onAddressBooksMenuPrepareVisibility() { var menu = $("contactFoldersMenu").down("ul");; var listElements = menu.childNodesWithTag("li"); var modifyOption = listElements[0]; + var newListOption = listElements[3]; var removeOption = listElements[5]; var exportOption = listElements[7]; + var importOption = listElements[8]; var sharingOption = listElements[listElements.length - 1]; // Disable the "Sharing" and "Modify" options when address book // is not owned by user if (folderOwner == UserLogin || IsSuperUser) { - modifyOption.removeClassName("disabled"); // WARNING: will fail for super users anyway - sharingOption.removeClassName("disabled"); + modifyOption.removeClassName("disabled"); // WARNING: will fail + // for super users + // anyway + var aclEditing = selected[0].getAttribute("acl-editing"); + if (aclEditing && aclEditing == "available") { + sharingOption.removeClassName("disabled"); + } + else { + sharingOption.addClassName("disabled"); + } } else { modifyOption.addClassName("disabled"); sharingOption.addClassName("disabled"); } + var listEditing = selected[0].getAttribute("list-editing"); + if (listEditing && listEditing == "available") { + newListOption.removeClassName("disabled"); + } + else { + newListOption.addClassName("disabled"); + } + /* Disable the "remove" and "export ab" options when address book is public */ if (folderOwner == "nobody") { exportOption.addClassName("disabled"); + importOption.addClassName("disabled"); removeOption.addClassName("disabled"); } else { exportOption.removeClassName("disabled"); - removeOption.removeClassName("disabled"); + importOption.removeClassName("disabled"); + if (selected[0].getAttribute("id") == "/personal") { + removeOption.addClassName("disabled"); + } + else { + removeOption.removeClassName("disabled"); + } } return true;