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
parent
aed62cab26
commit
eb90760b39
|
@ -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
2
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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -74,8 +74,6 @@
|
|||
|
||||
NSDictionary *modulesConstraints;
|
||||
|
||||
NSMutableArray *searchAttributes;
|
||||
|
||||
BOOL passwordPolicy;
|
||||
BOOL updateSambaNTLMPasswords;
|
||||
|
||||
|
|
|
@ -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 isEqualToString: @"."])
|
||||
|
||||
if (([escapedFilter length] == 0 && !listRequiresDot) || [escapedFilter isEqualToString: @"."])
|
||||
{
|
||||
[qs appendFormat: @"(%@='*')", CNField];
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", escapedFilter];
|
||||
fields = [NSMutableArray arrayWithArray: searchFields];
|
||||
if (criteria)
|
||||
criteriaList = [criteria objectEnumerator];
|
||||
else
|
||||
criteriaList = [[self searchFields] objectEnumerator];
|
||||
|
||||
fields = [NSMutableArray array];
|
||||
while (( currentCriteria = [criteriaList nextObject] ))
|
||||
{
|
||||
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];
|
||||
[fields addObject: CNField];
|
||||
searchFormat = [[[fields uniqueObjects] stringsWithFormat: fieldFormat]
|
||||
componentsJoinedByString: @" OR "];
|
||||
}
|
||||
else if ([[self searchFields] containsObject: currentCriteria])
|
||||
[fields addObject: currentCriteria];
|
||||
}
|
||||
|
||||
searchFormat = [[[fields uniqueObjects] stringsWithFormat: fieldFormat] componentsJoinedByString: @" OR "];
|
||||
[qs appendString: searchFormat];
|
||||
}
|
||||
|
||||
if (_filter && [_filter length])
|
||||
[qs appendFormat: @" AND %@", _filter];
|
||||
|
||||
if ([qs length])
|
||||
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;
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
NSString *_authenticationFilter;
|
||||
NSArray *_loginFields;
|
||||
NSArray *_mailFields;
|
||||
NSArray *_searchFields;
|
||||
NSString *_imapLoginField;
|
||||
NSString *_imapHostField;
|
||||
NSString *_sieveHostField;
|
||||
|
|
|
@ -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 ("];
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
@interface UIxContactsListActions : SOGoDirectAction
|
||||
{
|
||||
NSDictionary *requestData;
|
||||
|
||||
NSDictionary *currentContact;
|
||||
|
||||
NSArray *contactInfos;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
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];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue