Use address books search fields in Contacts module

Searches can now be scoped to one or multiple fields. Those fields are
now dynamic and can be defined using SearchFieldNames in external
contacts sources (SQL and LDAP).
pull/218/merge
Francis Lachapelle 2017-11-21 15:56:16 -05:00
parent aed62cab26
commit eb90760b39
19 changed files with 275 additions and 137 deletions

View File

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

2
NEWS
View File

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

View File

@ -43,7 +43,7 @@
@protocol SOGoContactFolder <NSObject>
- (NSArray *) lookupContactsWithFilter: (NSString *) filter
onCriteria: (NSString *) criteria
onCriteria: (NSArray *) criteria
sortBy: (NSString *) sortKey
ordering: (NSComparisonResult) sortOrdering
inDomain: (NSString *) domain;

View File

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

View File

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

View File

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

View File

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

View File

@ -74,8 +74,6 @@
NSDictionary *modulesConstraints;
NSMutableArray *searchAttributes;
BOOL passwordPolicy;
BOOL updateSambaNTLMPasswords;

View File

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

View File

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

View File

@ -40,6 +40,7 @@
NSString *_authenticationFilter;
NSArray *_loginFields;
NSArray *_mailFields;
NSArray *_searchFields;
NSString *_imapLoginField;
NSString *_imapHostField;
NSString *_sieveHostField;

View File

@ -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 <lmarcotte@inverse.ca>
* Francis Lachapelle <flachapelle@inverse.ca>
* 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 <SOGo/SOGoSystemDefaults.h>
#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 ("];

View File

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

View File

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

View File

@ -31,6 +31,8 @@
@interface UIxContactsListActions : SOGoDirectAction
{
NSDictionary *requestData;
NSDictionary *currentContact;
NSArray *contactInfos;

View File

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

View File

@ -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">
<md-button class="md-icon-button"
sg-search-cancel="addressbook.cancelSearch()"
label:aria-label="Back">
@ -330,10 +331,13 @@
</div>
</md-input-container>
<md-input-container flex="25">
<md-select label:aria-label="Search scope">
<md-option value="name_or_address" selected="selected"><var:string label:value="Name or Email"/></md-option>
<md-option value="category"><var:string label:value="Category"/></md-option>
<md-option value="organization"><var:string label:value="Organization"/></md-option>
<label><var:string label:value="Search scope"/></label>
<md-select multiple="multiple">
<md-optgroup label:label="Search scope">
<md-option
ng-value="field"
ng-repeat="field in ::addressbook.selectedFolder.searchFields">{{::field | loc}}</md-option>
</md-optgroup>
</md-select>
</md-input-container>
</form>

View File

@ -20,7 +20,7 @@
<md-input-container>
<input name="search" type="search"/>
</md-input-container>
<md-select class="sg-toolbar-sort md-contrast-light">
<md-select multiple>
<md-option value="subject">Subject</md-option>
<md-option value="sender">sender</md-option>
</md-select>
@ -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],

View File

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