diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index 6c3dd6db1..093961414 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -948,15 +948,15 @@ The returned value *must be unique across the whole SOGo installation* since it is used to identify the user in the `folder_info` database table. -|MailFieldNames +|MailFieldNames (optional) |An array of fields that returns the user's email addresses (defaults to `mail` when unset). Note that SOGo will always automatically strip the protocol value from the attribute if the attribute name is `proxyAddresses`. -|SearchFieldNames -|An array of fields to to match against the search string when filtering -users (defaults to `sn`, `displayName`, and `telephoneNumber` when -unset). +|SearchFieldNames (optional) +|An array of fields to match against the search string when filtering +users (defaults to `sn`, `displayName`, `cn`, `mail`, and `telephoneNumber` +when unset). |IMAPHostFieldName (optional) |The field that returns either an URI to the IMAP server as described @@ -1612,7 +1612,7 @@ SQL source: [cols="3,47a,50"] |======================================================================= -.20+^|D |SOGoUserSources +.21+^|D |SOGoUserSources |Parameter used to set the SQL and/or LDAP sources used for authentication and global address books. Multiple sources can be specified as an array of dictionaries. A dictionary that defines a SQL @@ -1688,6 +1688,10 @@ additional email addresses (beside the `mail` column) for each user. Values must be unique and not appear in more than one column. Space-separated values allowed in all *additional* columns (besides in `mail`). +|SearchFieldNames (optional) +|An array of fields to match against the search string when filtering +users (defaults to `c_cn` and `mail` when unset). + |IMAPHostFieldName (optional) |The field that returns the IMAP hostname for the user. diff --git a/NEWS b/NEWS index 2c37970c7..b0f16cfc4 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ New features - [core] can now invite attendees to exceptions only (#2561) - [core] add support for module constraints in SQL sources - [core] add support for listRequiresDot in SQL sources + - [web] add support for SearchFieldNames in SQL sources - [web] display freebusy information of owner in appointment editor - [web] register SOGo as a handler for the mailto scheme (#1223) - [web] new events list view where events are grouped by day @@ -15,6 +16,7 @@ Enhancements - [web] added Simplified Chinese (zh_CN) translation - thanks to Thomas Kuiper - [web] now also give modify permission when selecting all calendar rights - [web] allow edition of IMAP flags associated to mail labels + - [web] search scope of address book is now respected Bug fixes - [core] yearly repeating events are not shown in web calendar (#4237) diff --git a/SoObjects/Contacts/SOGoContactFolder.h b/SoObjects/Contacts/SOGoContactFolder.h index 47b098548..7d52b232d 100644 --- a/SoObjects/Contacts/SOGoContactFolder.h +++ b/SoObjects/Contacts/SOGoContactFolder.h @@ -43,7 +43,7 @@ @protocol SOGoContactFolder - (NSArray *) lookupContactsWithFilter: (NSString *) filter - onCriteria: (NSString *) criteria + onCriteria: (NSArray *) criteria sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering inDomain: (NSString *) domain; diff --git a/SoObjects/Contacts/SOGoContactFolders.m b/SoObjects/Contacts/SOGoContactFolders.m index 5a0d81333..3e7236e17 100644 --- a/SoObjects/Contacts/SOGoContactFolders.m +++ b/SoObjects/Contacts/SOGoContactFolders.m @@ -459,7 +459,7 @@ Class SOGoContactSourceFolderK; folder = [sortedFolders objectAtIndex: i]; //NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]); contacts = [folder lookupContactsWithFilter: theFilter - onCriteria: @"name_or_address" + onCriteria: nil sortBy: @"c_cn" ordering: NSOrderedAscending inDomain: domain]; diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.h b/SoObjects/Contacts/SOGoContactGCSFolder.h index 4447aa186..01789a6dd 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.h +++ b/SoObjects/Contacts/SOGoContactGCSFolder.h @@ -37,7 +37,7 @@ } - (void) fixupContactRecord: (NSMutableDictionary *) contactRecord; - (EOQualifier *) qualifierForFilter: (NSString *) filter - onCriteria: (NSString *) criteria; + onCriteria: (NSArray *) criteria; - (NSDictionary *) lookupContactWithName: (NSString *) aName; - (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier; - (NSArray *) lookupContactsFields: (NSArray *) fields diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 7ce3feecd..927b55ad1 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -79,6 +79,20 @@ static NSArray *folderListingFields = nil; [super dealloc]; } +- (NSArray *) searchFields +{ + static NSArray *searchFields = nil; + + if (!searchFields) + { + // "name" expands to c_sn, c_givenname and c_cn + searchFields = [NSArray arrayWithObjects: @"name", @"c_mail", @"c_categories", @"c_o", nil]; + [searchFields retain]; + } + + return searchFields; +} + - (Class) objectClassForContent: (NSString *) content { CardGroup *cardEntry; @@ -183,39 +197,42 @@ static NSArray *folderListingFields = nil; } - (EOQualifier *) qualifierForFilter: (NSString *) filter - onCriteria: (NSString *) criteria + onCriteria: (NSArray *) criteria { - NSString *qs; + NSEnumerator *criteriaList; + NSMutableArray *filters; + NSString *filterFormat, *currentCriteria, *qs; EOQualifier *qualifier; + qualifier = nil; if ([filter length] > 0) { filter = [filter asSafeSQLString]; - if ([criteria isEqualToString: @"name_or_address"]) - qs = [NSString stringWithFormat: - @"(c_sn isCaseInsensitiveLike: '%%%@%%') OR " - @"(c_givenname isCaseInsensitiveLike: '%%%@%%') OR " - @"(c_cn isCaseInsensitiveLike: '%%%@%%') OR " - @"(c_mail isCaseInsensitiveLike: '%%%@%%')", - filter, filter, filter, filter]; - else if ([criteria isEqualToString: @"category"]) - qs = [NSString stringWithFormat: - @"(c_categories isCaseInsensitiveLike: '%%%@%%')", - filter]; - else if ([criteria isEqualToString: @"organization"]) - qs = [NSString stringWithFormat: - @"(c_o isCaseInsensitiveLike: '%%%@%%')", - filter]; + filters = [NSMutableArray array]; + filterFormat = [NSString stringWithFormat: @"(%%@ isCaseInsensitiveLike: '%%%%%@%%%%')", filter]; + if (criteria) + criteriaList = [criteria objectEnumerator]; else - qs = @"(1 == 0)"; + criteriaList = [[self searchFields] objectEnumerator]; - if (qs) - qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; - else - qualifier = nil; + while (( currentCriteria = [criteriaList nextObject] )) + { + if ([currentCriteria isEqualToString: @"name"]) + { + [filters addObject: @"c_sn"]; + [filters addObject: @"c_givenname"]; + [filters addObject: @"c_cn"]; + } + else if ([[self searchFields] containsObject: currentCriteria]) + [filters addObject: currentCriteria]; + } + + if ([filters count]) + { + qs = [[[filters uniqueObjects] stringsWithFormat: filterFormat] componentsJoinedByString: @" OR "]; + qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; + } } - else - qualifier = nil; return qualifier; } @@ -357,7 +374,7 @@ static NSArray *folderListingFields = nil; * The domain is therefore ignored. */ - (NSArray *) lookupContactsWithFilter: (NSString *) filter - onCriteria: (NSString *) criteria + onCriteria: (NSArray *) criteria sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering inDomain: (NSString *) domain diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.m b/SoObjects/Contacts/SOGoContactSourceFolder.m index a74515fae..ce1195a18 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m @@ -115,6 +115,11 @@ return isPersonalSource; } +- (NSArray *) searchFields +{ + return [source searchFields]; +} + - (BOOL) listRequiresDot { return [source listRequiresDot]; @@ -391,7 +396,7 @@ } - (NSArray *) lookupContactsWithFilter: (NSString *) filter - onCriteria: (NSString *) criteria + onCriteria: (NSArray *) criteria sortBy: (NSString *) sortKey ordering: (NSComparisonResult) sortOrdering inDomain: (NSString *) domain @@ -401,10 +406,10 @@ result = nil; - if (([filter length] > 0 && [criteria isEqualToString: @"name_or_address"]) - || ![source listRequiresDot]) + if ([filter length] > 0 || ![source listRequiresDot]) { records = [source fetchContactsMatching: filter + withCriteria: criteria inDomain: domain]; [childRecords setObjects: records forKeys: [records objectsForKey: @"c_name" diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 60762950e..df02dd461 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -74,8 +74,6 @@ NSDictionary *modulesConstraints; - NSMutableArray *searchAttributes; - BOOL passwordPolicy; BOOL updateSambaNTLMPasswords; diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index d5da0555c..a55565d21 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -92,7 +92,9 @@ static Class NSStringK; mailFields = [NSArray arrayWithObject: @"mail"]; [mailFields retain]; contactMapping = nil; - searchFields = [NSArray arrayWithObjects: @"sn", @"displayname", @"telephonenumber", nil]; + // "mail" expands to all entries of MailFieldNames + // "name" expands to sn, displayname and cn + searchFields = [NSArray arrayWithObjects: @"name", @"mail", @"telephonenumber", nil]; [searchFields retain]; groupObjectClasses = [NSArray arrayWithObjects: @"group", @"groupofnames", @"groupofuniquenames", @"posixgroup", nil]; [groupObjectClasses retain]; @@ -105,7 +107,6 @@ static Class NSStringK; _userPasswordAlgorithm = nil; listRequiresDot = YES; - searchAttributes = nil; passwordPolicy = NO; updateSambaNTLMPasswords = NO; @@ -149,7 +150,6 @@ static Class NSStringK; [sourceID release]; [modulesConstraints release]; [_scope release]; - [searchAttributes release]; [domain release]; [kindField release]; [multipleBookingsField release]; @@ -373,6 +373,11 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses return listRequiresDot; } +- (NSArray *) searchFields +{ + return searchFields; +} + - (void) setContactMapping: (NSDictionary *) newMapping andObjectClasses: (NSArray *) newObjectClasses { @@ -761,41 +766,57 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses * @return a EOQualifier matching the filter */ - (EOQualifier *) _qualifierForFilter: (NSString *) filter + onCriteria: (NSArray *) criteria { + NSEnumerator *criteriaList; NSMutableArray *fields; - NSString *fieldFormat, *searchFormat, *escapedFilter; + NSString *fieldFormat, *currentCriteria, *searchFormat, *escapedFilter; EOQualifier *qualifier; NSMutableString *qs; escapedFilter = SafeLDAPCriteria(filter); - if ([escapedFilter length] > 0) + qs = [NSMutableString string]; + + if (([escapedFilter length] == 0 && !listRequiresDot) || [escapedFilter isEqualToString: @"."]) { - qs = [NSMutableString string]; - if ([escapedFilter isEqualToString: @"."]) - [qs appendFormat: @"(%@='*')", CNField]; + [qs appendFormat: @"(%@='*')", CNField]; + } + else + { + fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", escapedFilter]; + if (criteria) + criteriaList = [criteria objectEnumerator]; else + criteriaList = [[self searchFields] objectEnumerator]; + + fields = [NSMutableArray array]; + while (( currentCriteria = [criteriaList nextObject] )) { - fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", escapedFilter]; - fields = [NSMutableArray arrayWithArray: searchFields]; - [fields addObjectsFromArray: mailFields]; - [fields addObject: CNField]; - searchFormat = [[[fields uniqueObjects] stringsWithFormat: fieldFormat] - componentsJoinedByString: @" OR "]; - [qs appendString: searchFormat]; + if ([currentCriteria isEqualToString: @"name"]) + { + [fields addObject: @"sn"]; + [fields addObject: @"displayname"]; + [fields addObject: @"cn"]; + } + else if ([currentCriteria isEqualToString: @"mail"]) + { + // Expand to all mail fields + [fields addObject: currentCriteria]; + [fields addObjectsFromArray: mailFields]; + } + else if ([[self searchFields] containsObject: currentCriteria]) + [fields addObject: currentCriteria]; } - if (_filter && [_filter length]) - [qs appendFormat: @" AND %@", _filter]; + searchFormat = [[[fields uniqueObjects] stringsWithFormat: fieldFormat] componentsJoinedByString: @" OR "]; + [qs appendString: searchFormat]; + } - qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; - } - else if (!listRequiresDot) - { - qs = [NSMutableString stringWithFormat: @"(%@='*')", CNField]; - if ([_filter length]) - [qs appendFormat: @" AND %@", _filter]; - qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; - } + if (_filter && [_filter length]) + [qs appendFormat: @" AND %@", _filter]; + + if ([qs length]) + qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; else qualifier = nil; @@ -832,6 +853,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses return [EOQualifier qualifierWithQualifierFormat: qs]; } +/* - (NSArray *) _constraintsFields { NSMutableArray *fields; @@ -845,6 +867,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses return fields; } +*/ /* This is required for SQL sources when DomainFieldName is enabled. * For LDAP, simply discard the domain and call the original method */ @@ -1202,6 +1225,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses } - (NSArray *) fetchContactsMatching: (NSString *) match + withCriteria: (NSArray *) criteria inDomain: (NSString *) domain { NGLdapConnection *ldapConnection; @@ -1216,7 +1240,7 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses if ([match length] > 0 || !listRequiresDot) { ldapConnection = [self _ldapConnection]; - qualifier = [self _qualifierForFilter: match]; + qualifier = [self _qualifierForFilter: match onCriteria: criteria]; attributes = [NSArray arrayWithObject: @"*"]; if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) diff --git a/SoObjects/SOGo/SOGoSource.h b/SoObjects/SOGo/SOGoSource.h index 572efb569..d3c53bcad 100644 --- a/SoObjects/SOGo/SOGoSource.h +++ b/SoObjects/SOGo/SOGoSource.h @@ -39,6 +39,7 @@ inDomain: (NSString *) domain; - (NSString *) domain; +- (NSArray *) searchFields; /* requires a "." to obtain the full list of contacts */ - (void) setListRequiresDot: (BOOL) aBool; @@ -63,6 +64,7 @@ - (NSArray *) allEntryIDs; - (NSArray *) allEntryIDsVisibleFromDomain: (NSString *) domain; - (NSArray *) fetchContactsMatching: (NSString *) filter + withCriteria: (NSArray *) criteria inDomain: (NSString *) domain; - (void) setSourceID: (NSString *) newSourceID; diff --git a/SoObjects/SOGo/SQLSource.h b/SoObjects/SOGo/SQLSource.h index b3451df59..e84b4c05b 100644 --- a/SoObjects/SOGo/SQLSource.h +++ b/SoObjects/SOGo/SQLSource.h @@ -40,6 +40,7 @@ NSString *_authenticationFilter; NSArray *_loginFields; NSArray *_mailFields; + NSArray *_searchFields; NSString *_imapLoginField; NSString *_imapHostField; NSString *_sieveHostField; diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m index e023c26d6..cff7cc43a 100644 --- a/SoObjects/SOGo/SQLSource.m +++ b/SoObjects/SOGo/SQLSource.m @@ -1,9 +1,8 @@ /* SQLSource.h - this file is part of SOGo * - * Copyright (C) 2009-2012 Inverse inc. + * Copyright (C) 2009-2017 Inverse inc. * - * Authors: Ludovic Marcotte - * Francis Lachapelle + * This file is part of SOGo. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +34,7 @@ #import +#import "NSArray+Utilities.h" #import "NSString+Utilities.h" #import "NSString+Crypto.h" @@ -90,6 +90,9 @@ _authenticationFilter = nil; _loginFields = nil; _mailFields = nil; + // "mail" expands to all entries of MailFieldNames + _searchFields = [NSArray arrayWithObjects: @"c_cn", @"mail", nil]; + [_searchFields retain]; _userPasswordAlgorithm = nil; _viewURL = nil; _kindField = nil; @@ -109,6 +112,7 @@ [_authenticationFilter release]; [_loginFields release]; [_mailFields release]; + [_searchFields release]; [_userPasswordAlgorithm release]; [_viewURL release]; [_kindField release]; @@ -140,6 +144,8 @@ ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]); ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]); ASSIGN(_modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]); + if ([udSource objectForKey: @"SearchFieldNames"]) + ASSIGN(_searchFields, [udSource objectForKey: @"SearchFieldNames"]); if ([udSource objectForKey: @"prependPasswordScheme"]) _prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue]; else @@ -157,7 +163,7 @@ #warning this domain code has no effect yet if ([sourceDomain length]) - ASSIGN (_domain, sourceDomain); + ASSIGN(_domain, sourceDomain); if (!_viewURL) { @@ -173,6 +179,11 @@ return _domain; } +- (NSArray *) searchFields +{ + return _searchFields; +} + - (BOOL) _isPassword: (NSString *) plainPassword equalTo: (NSString *) encryptedPassword { @@ -369,6 +380,7 @@ return didChange; } +/* - (NSString *) _whereClauseFromArray: (NSArray *) theArray value: (NSString *) theValue exact: (BOOL) theBOOL @@ -388,6 +400,7 @@ return s; } +*/ - (void) _fillConstraintsForModule: (NSString *) module intoRecord: (NSMutableDictionary *) record @@ -562,7 +575,7 @@ [emails addObjectsFromArray: [s componentsSeparatedByString: @" "]]; } - [response setObject: emails forKey: @"c_emails"]; + [response setObject: [emails uniqueObjects] forKey: @"c_emails"]; if (_imapHostField) { value = [response objectForKey: _imapHostField]; @@ -792,14 +805,16 @@ } - (NSArray *) fetchContactsMatching: (NSString *) filter + withCriteria: (NSArray *) criteria inDomain: (NSString *) domain { EOAdaptorChannel *channel; - NSMutableArray *results; + NSEnumerator *criteriaList; + NSMutableArray *fields, *results; GCSChannelManager *cm; NSException *ex; NSMutableString *sql; - NSString *lowerFilter; + NSString *lowerFilter, *filterFormat, *currentCriteria, *qs; results = [NSMutableArray array]; @@ -809,22 +824,40 @@ channel = [cm acquireOpenChannelForURL: _viewURL]; if (channel) { - lowerFilter = [filter lowercaseString]; - lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"]; - - sql = [NSMutableString stringWithFormat: (@"SELECT *" - @" FROM %@" - @" WHERE" - @" (LOWER(c_cn) LIKE '%%%@%%'" - @" OR LOWER(mail) LIKE '%%%@%%'"), - [_viewURL gcsTableName], - lowerFilter, lowerFilter]; - - if (_mailFields && [_mailFields count] > 0) + fields = [NSMutableArray array]; + if ([filter length]) { - [sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]]; + lowerFilter = [filter lowercaseString]; + lowerFilter = [lowerFilter asSafeSQLString]; + filterFormat = [NSString stringWithFormat: @"LOWER(%%@) LIKE '%%%%%@%%%%'", lowerFilter]; + if (criteria) + criteriaList = [criteria objectEnumerator]; + else + criteriaList = [[self searchFields] objectEnumerator]; + + while (( currentCriteria = [criteriaList nextObject] )) + { + if ([currentCriteria isEqualToString: @"mail"]) + { + // Expand to all mail fields + [fields addObject: currentCriteria]; + if (_mailFields) + [fields addObjectsFromArray: _mailFields]; + } + else if ([[self searchFields] containsObject: currentCriteria]) + [fields addObject: currentCriteria]; + } } + sql = [NSMutableString stringWithFormat: @"SELECT * FROM %@ WHERE (", [_viewURL gcsTableName]]; + + if ([fields count]) + { + qs = [[[fields uniqueObjects] stringsWithFormat: filterFormat] componentsJoinedByString: @" OR "]; + [sql appendString: qs]; + } + else + [sql appendString: @"1 = 1"]; [sql appendString: @")"]; if (_domainField) @@ -832,8 +865,7 @@ if ([domain length]) { EOQualifier *domainQualifier; - domainQualifier = - [self _visibleDomainsQualifierFromDomain: domain]; + domainQualifier = [self _visibleDomainsQualifierFromDomain: domain]; if (domainQualifier) { [sql appendFormat: @" AND ("]; diff --git a/UI/Contacts/English.lproj/Localizable.strings b/UI/Contacts/English.lproj/Localizable.strings index 5ba619472..500aea21c 100644 --- a/UI/Contacts/English.lproj/Localizable.strings +++ b/UI/Contacts/English.lproj/Localizable.strings @@ -45,6 +45,30 @@ "Carbon Copy (Cc)" = "Carbon Copy (Cc)"; "Blind Carbon Copy (Bcc)" = "Blind Carbon Copy (Bcc)"; +/* Search scope: name fields */ +"name" = "Name"; + +/* Search scope: name fields */ +"c_cn" = "Name"; + +/* Search scope: mail fields */ +"mail" = "Mail"; + +/* Search scope: mail fields */ +"c_mail" = "Mail"; + +/* Search scope: telephone field */ +"telephonenumber" = "Telephone"; + +/* Search scope: categories field */ +"c_categories" = "Categories"; + +/* Search scope: categories field */ +"vcardcategories" = "Categories"; + +/* Search scope: organization field */ +"c_o" = "Organization"; + /* Subheader of empty addressbook */ "No contact" = "No contact"; diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index 1fcb8e879..d100faee3 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -199,7 +199,7 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK; folder = [sortedFolders objectAtIndex: i]; //NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]); contacts = [folder lookupContactsWithFilter: searchText - onCriteria: @"name_or_address" + onCriteria: nil sortBy: @"c_cn" ordering: NSOrderedAscending inDomain: domain]; @@ -343,6 +343,7 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK; && [currentFolder listRequiresDot]], @"listRequiresDot", acls, @"acls", urls, @"urls", + [currentFolder searchFields], @"searchFields", nil]; [foldersAttrs addObject: folderAttrs]; } diff --git a/UI/Contacts/UIxContactsListActions.h b/UI/Contacts/UIxContactsListActions.h index 8813b873d..42279902e 100644 --- a/UI/Contacts/UIxContactsListActions.h +++ b/UI/Contacts/UIxContactsListActions.h @@ -31,6 +31,8 @@ @interface UIxContactsListActions : SOGoDirectAction { + NSDictionary *requestData; + NSDictionary *currentContact; NSArray *contactInfos; diff --git a/UI/Contacts/UIxContactsListActions.m b/UI/Contacts/UIxContactsListActions.m index 6358eb3bd..1a1939d28 100644 --- a/UI/Contacts/UIxContactsListActions.m +++ b/UI/Contacts/UIxContactsListActions.m @@ -54,6 +54,7 @@ { if ((self = [super init])) { + requestData = nil; contactInfos = nil; sortedIDs = nil; } @@ -63,6 +64,7 @@ - (void) dealloc { + [requestData release]; [contactInfos release]; [sortedIDs release]; [super dealloc]; @@ -70,6 +72,20 @@ /* accessors */ +- (NSDictionary *) requestData +{ + WORequest *rq; + + if (!requestData) + { + rq = [context request]; + requestData = [[rq contentAsString] objectFromJSONString]; + [requestData retain]; + } + + return requestData; +} + - (NSString *) defaultSortKey { return @"c_cn"; @@ -78,7 +94,6 @@ - (NSString *) sortKey { NSString *s; - WORequest *rq; static NSArray *sortKeys = nil; if (!sortKeys) @@ -88,8 +103,7 @@ [sortKeys retain]; } - rq = [context request]; - s = [rq formValueForKey: @"sort"]; + s = [[self requestData] objectForKey: @"sort"]; if (![s length] || ![sortKeys containsObject: s]) s = [self defaultSortKey]; @@ -102,8 +116,8 @@ NSString *ascending, *sort; SOGoUserSettings *us; - sort = [[context request] formValueForKey: @"sort"]; - ascending = [[context request] formValueForKey: @"asc"]; + sort = [[self requestData] objectForKey: @"sort"]; + ascending = [[self requestData] objectForKey: @"asc"]; if ([sort length]) { @@ -125,14 +139,13 @@ - (NSArray *) contactInfos { id folder; - NSString *ascending, *searchText, *valueText; - NSArray *results, *fields; + NSString *ascending, *valueText; + NSArray *results, *searchFields, *fields; NSMutableArray *filteredContacts, *headers; - NSDictionary *contact; + NSDictionary *data, *contact; BOOL excludeLists; NSComparisonResult ordering; NSUInteger max, count; - WORequest *rq; unsigned int i; [self saveSortValue]; @@ -140,23 +153,26 @@ if (!contactInfos) { folder = [self clientObject]; - rq = [context request]; + data = [self requestData]; - ascending = [rq formValueForKey: @"asc"]; - ordering = ((![ascending length] || [ascending boolValue]) + ascending = [data objectForKey: @"asc"]; + ordering = ((!ascending || [ascending boolValue]) ? NSOrderedAscending : NSOrderedDescending); - searchText = [rq formValueForKey: @"search"]; - if ([searchText length] > 0) - valueText = [rq formValueForKey: @"value"]; + searchFields = [data objectForKey: @"search"]; + if ([searchFields isKindOfClass: [NSArray class]] && [searchFields count] > 0) + valueText = [data objectForKey: @"value"]; else - valueText = nil; + { + searchFields = nil; + valueText = nil; + } - excludeLists = [[rq formValueForKey: @"excludeLists"] boolValue]; + excludeLists = [[data objectForKey: @"excludeLists"] boolValue]; [contactInfos release]; results = [folder lookupContactsWithFilter: valueText - onCriteria: searchText + onCriteria: searchFields sortBy: [self sortKey] ordering: ordering inDomain: [[context activeUser] domain]]; @@ -204,16 +220,15 @@ - (NSArray *) sortedIDs { id folder; - NSString *ascending, *searchText, *valueText; - NSArray *fields, *records; - NSDictionary *record; + NSString *ascending, *valueText; + NSArray *searchFields, *fields, *records; + NSDictionary *data, *record; NSEnumerator *recordsList; NSMutableArray *ids; BOOL excludeLists; EOKeyValueQualifier *kvQualifier; EOSortOrdering *ordering; EOQualifier *qualifier; - WORequest *rq; SEL compare; folder = [self clientObject]; @@ -221,12 +236,12 @@ if (!sortedIDs && [folder isKindOfClass: [SOGoContactGCSFolder class]]) { fields = [NSArray arrayWithObjects: @"c_name", nil]; - rq = [context request]; + data = [self requestData]; qualifier = nil; // ORDER BY clause - ascending = [rq formValueForKey: @"asc"]; - if (![ascending length] || [ascending boolValue]) + ascending = [data valueForKey: @"asc"]; + if (!ascending || [ascending boolValue]) compare = EOCompareAscending; else compare = EOCompareDescending; @@ -234,14 +249,14 @@ selector: compare]; // WHERE clause - searchText = [rq formValueForKey: @"search"]; - if ([searchText length] > 0) + searchFields = (NSArray *)[data objectForKey: @"search"]; + if ([searchFields count] > 0) { - valueText = [rq formValueForKey: @"value"]; + valueText = [data objectForKey: @"value"]; qualifier = [(SOGoContactGCSFolder *) folder qualifierForFilter: valueText - onCriteria: searchText]; + onCriteria: searchFields]; } - excludeLists = [[rq formValueForKey: @"excludeLists"] boolValue]; + excludeLists = [[data objectForKey: @"excludeLists"] boolValue]; if (excludeLists) { kvQualifier = [[EOKeyValueQualifier alloc] @@ -337,7 +352,7 @@ * @apiExample {curl} Example usage: * curl -i http://localhost/SOGo/so/sogo1/Contacts/personal/view?search=name_or_address\&value=Bob * - * @apiParam {Boolean} [partial] Send all contacts IDs and headers of a the first 50 contacts. Defaults to false. + * @apiParam {Boolean} [partial] Send all contacts IDs and headers of the first 50 contacts. Defaults to false. * @apiParam {Boolean} [asc] Descending sort when false. Defaults to true (ascending). * @apiParam {String} [sort] Sort field. Either c_cn, c_mail, c_screenname, c_o, or c_telephonenumber. * @apiParam {String} [search] Field criteria. Either name_or_address, category, or organization. @@ -383,7 +398,7 @@ [self cardDavURL], @"cardDavURL", [self publicCardDavURL], @"publicCardDavURL", nil]; - partial = [[context request] formValueForKey: @"partial"]; + partial = [[self requestData] objectForKey: @"partial"]; if ([partial intValue] && [folder isKindOfClass: [SOGoContactGCSFolder class]]) { @@ -427,11 +442,9 @@ { NSArray *ids, *headers; NSDictionary *data; - WORequest *request; WOResponse *response; - request = [context request]; - data = [[request contentAsString] objectFromJSONString]; + data = [self requestData]; if (![[data objectForKey: @"ids"] isKindOfClass: [NSArray class]] || [[data objectForKey: @"ids"] count] == 0) { @@ -463,11 +476,9 @@ NSMutableDictionary *uniqueContacts; unsigned int i; NSSortDescriptor *commonNameDescriptor; - WORequest *rq; - rq = [context request]; - excludeLists = [[rq formValueForKey: @"excludeLists"] boolValue]; - searchText = [rq formValueForKey: @"search"]; + excludeLists = [[[self requestData] objectForKey: @"excludeLists"] boolValue]; + searchText = [[self requestData] objectForKey: @"search"]; if ([searchText length] > 0) { NS_DURING @@ -482,7 +493,7 @@ domain = [[context activeUser] domain]; uniqueContacts = [NSMutableDictionary dictionary]; contacts = [folder lookupContactsWithFilter: searchText - onCriteria: @"name_or_address" + onCriteria: nil sortBy: @"c_cn" ordering: NSOrderedAscending inDomain: domain]; diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index 85f101147..074260f45 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -317,7 +317,8 @@ layout="row" ng-show="addressbook.mode.search" sg-search="addressbook.selectedFolder.$filter(searchText, { search: searchField })" - sg-allow-dot="addressbook.selectedFolder.listRequiresDot"> + sg-allow-dot="addressbook.selectedFolder.listRequiresDot" + sg-search-fields="addressbook.selectedFolder.searchFields"> @@ -330,10 +331,13 @@ - - - - + + + + {{::field | loc}} + diff --git a/UI/WebServerResources/js/Common/sgSearch.directive.js b/UI/WebServerResources/js/Common/sgSearch.directive.js index d791f5615..3325475db 100644 --- a/UI/WebServerResources/js/Common/sgSearch.directive.js +++ b/UI/WebServerResources/js/Common/sgSearch.directive.js @@ -20,7 +20,7 @@ - + Subject sender @@ -67,6 +67,9 @@ // Associate the sg-allow-dot parameter (boolean) to the controller controller.allowDot = $parse(iElement.attr('sg-allow-dot'))(scope); + // Associate the sg-search-fields parameter (array) to the controller + controller.fields = $parse(iElement.attr('sg-search-fields'))(scope); + // Associate callback to controller controller.doSearch = $parse(iElement.attr('sg-search')); @@ -114,6 +117,14 @@ } }; + if ($element.attr('sg-search-fields')) { + var waitforFieldsOnce = $scope.$watch(vm.fields, function(value) { + // Select all fields by default + vm.searchField = _.clone(vm.fields); + waitforFieldsOnce(); + }); + } + // Method to call on data changes vm.onChange = function() { var form = $scope[vm.formName], diff --git a/UI/WebServerResources/js/Contacts/AddressBook.service.js b/UI/WebServerResources/js/Contacts/AddressBook.service.js index 0697aa8e1..ea8abbd4e 100644 --- a/UI/WebServerResources/js/Contacts/AddressBook.service.js +++ b/UI/WebServerResources/js/Contacts/AddressBook.service.js @@ -43,7 +43,7 @@ $Card: Card, $$Acl: Acl, $Preferences: Preferences, - $query: {search: 'name_or_address', value: '', sort: 'c_cn', asc: 1}, + $query: {value: '', sort: 'c_cn', asc: 1}, activeUser: Settings.activeUser(), selectedFolder: null, $refreshTimeout: null @@ -497,7 +497,7 @@ query.value = search; return _this.$id().then(function(addressbookId) { - var futureData = AddressBook.$$resource.fetch(addressbookId, 'view', query); + var futureData = AddressBook.$$resource.post(addressbookId, 'view', query); if (dry) { return futureData.then(function(response) {