ModulesConstraints and listRequiresDot for SQL
SQL sources used for authentication can now have module constraints. Entries of SQL sources used as address books can now be displayed automatically.pull/218/merge
parent
08ec68c07c
commit
a2129f3e4a
|
@ -1612,7 +1612,7 @@ SQL source:
|
||||||
|
|
||||||
[cols="3,47a,50"]
|
[cols="3,47a,50"]
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
.18+^|D |SOGoUserSources
|
.20+^|D |SOGoUserSources
|
||||||
|Parameter used to set the SQL and/or LDAP sources used for
|
|Parameter used to set the SQL and/or LDAP sources used for
|
||||||
authentication and global address books. Multiple sources can be
|
authentication and global address books. Multiple sources can be
|
||||||
specified as an array of dictionaries. A dictionary that defines a SQL
|
specified as an array of dictionaries. A dictionary that defines a SQL
|
||||||
|
@ -1719,6 +1719,23 @@ to the user.
|
||||||
|
|
||||||
See the _Multi-domains Configuration_ section in this document for more
|
See the _Multi-domains Configuration_ section in this document for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
|
|listRequiresDot (optional)
|
||||||
|
|If set to `YES`, listing of this SQL source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitely typing a single dot.
|
||||||
|
Defaults to `YES` when unset.
|
||||||
|
|
||||||
|
|ModulesConstraints (optional)
|
||||||
|
|Limits the access of any module through a constraint based on a SQL
|
||||||
|
column; must be a dictionary with keys `Mail`, and/or `Calendar`,
|
||||||
|
and/or `ActiveSync` for example:
|
||||||
|
|
||||||
|
----
|
||||||
|
ModulesConstraints = {
|
||||||
|
Calendar = {
|
||||||
|
c_ou = employees;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
----
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
|
|
||||||
Here is an example of an SQL-based authentication and address book
|
Here is an example of an SQL-based authentication and address book
|
||||||
|
|
2
NEWS
2
NEWS
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
New features
|
New features
|
||||||
- [core] can now invite attendees to exceptions only (#2561)
|
- [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] display freebusy information of owner in appointment editor
|
- [web] display freebusy information of owner in appointment editor
|
||||||
- [web] register SOGo as a handler for the mailto scheme (#1223)
|
- [web] register SOGo as a handler for the mailto scheme (#1223)
|
||||||
- [web] new events list view where events are grouped by day
|
- [web] new events list view where events are grouped by day
|
||||||
|
|
|
@ -229,8 +229,7 @@ static Class NSStringK;
|
||||||
else
|
else
|
||||||
queryTimeout = [dd ldapQueryTimeout];
|
queryTimeout = [dd ldapQueryTimeout];
|
||||||
|
|
||||||
ASSIGN(modulesConstraints,
|
ASSIGN(modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
|
||||||
[udSource objectForKey: @"ModulesConstraints"]);
|
|
||||||
ASSIGN(_filter, [udSource objectForKey: @"filter"]);
|
ASSIGN(_filter, [udSource objectForKey: @"filter"]);
|
||||||
ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
|
ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
|
||||||
ASSIGN(_scope, ([udSource objectForKey: @"scope"]
|
ASSIGN(_scope, ([udSource objectForKey: @"scope"]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* SQLSource.h - this file is part of SOGo
|
/* SQLSource.h - this file is part of SOGo
|
||||||
*
|
*
|
||||||
* Copyright (C) 2009-2011 Inverse inc.
|
* Copyright (C) 2009-2017 Inverse inc.
|
||||||
*
|
*
|
||||||
* Authors: Ludovic Marcotte <lmarcotte@inverse.ca>
|
* Authors: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||||
* Francis Lachapelle <flachapelle@invers.ca>
|
* Francis Lachapelle <flachapelle@invers.ca>
|
||||||
|
@ -50,6 +50,10 @@
|
||||||
/* resources handling */
|
/* resources handling */
|
||||||
NSString *_kindField;
|
NSString *_kindField;
|
||||||
NSString *_multipleBookingsField;
|
NSString *_multipleBookingsField;
|
||||||
|
|
||||||
|
BOOL _listRequiresDot;
|
||||||
|
|
||||||
|
NSDictionary *_modulesConstraints;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
*
|
*
|
||||||
* A SQL source can be defined like this:
|
* A SQL source can be defined like this:
|
||||||
*
|
*
|
||||||
* {
|
* {
|
||||||
* id = zot;
|
* id = zot;
|
||||||
* type = sql;
|
* type = sql;
|
||||||
* viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view";
|
* viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view";
|
||||||
|
@ -96,6 +96,8 @@
|
||||||
_multipleBookingsField = nil;
|
_multipleBookingsField = nil;
|
||||||
_imapHostField = nil;
|
_imapHostField = nil;
|
||||||
_sieveHostField = nil;
|
_sieveHostField = nil;
|
||||||
|
_listRequiresDot = YES;
|
||||||
|
_modulesConstraints = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
@ -114,13 +116,16 @@
|
||||||
[_domainField release];
|
[_domainField release];
|
||||||
[_imapHostField release];
|
[_imapHostField release];
|
||||||
[_sieveHostField release];
|
[_sieveHostField release];
|
||||||
|
[_modulesConstraints release];
|
||||||
|
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id) initFromUDSource: (NSDictionary *) udSource
|
- (id) initFromUDSource: (NSDictionary *) udSource
|
||||||
inDomain: (NSString *) sourceDomain
|
inDomain: (NSString *) sourceDomain
|
||||||
{
|
{
|
||||||
|
NSNumber *dotValue;
|
||||||
|
|
||||||
self = [self init];
|
self = [self init];
|
||||||
|
|
||||||
ASSIGN(_sourceID, [udSource objectForKey: @"id"]);
|
ASSIGN(_sourceID, [udSource objectForKey: @"id"]);
|
||||||
|
@ -134,17 +139,22 @@
|
||||||
ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]);
|
ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]);
|
||||||
ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]);
|
ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]);
|
||||||
ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]);
|
ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]);
|
||||||
|
ASSIGN(_modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
|
||||||
if ([udSource objectForKey: @"prependPasswordScheme"])
|
if ([udSource objectForKey: @"prependPasswordScheme"])
|
||||||
_prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue];
|
_prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue];
|
||||||
else
|
else
|
||||||
_prependPasswordScheme = NO;
|
_prependPasswordScheme = NO;
|
||||||
|
|
||||||
if (!_userPasswordAlgorithm)
|
if (!_userPasswordAlgorithm)
|
||||||
_userPasswordAlgorithm = @"none";
|
_userPasswordAlgorithm = @"none";
|
||||||
|
|
||||||
if ([udSource objectForKey: @"viewURL"])
|
if ([udSource objectForKey: @"viewURL"])
|
||||||
_viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]];
|
_viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]];
|
||||||
|
|
||||||
|
dotValue = [udSource objectForKey: @"listRequiresDot"];
|
||||||
|
if (dotValue)
|
||||||
|
[self setListRequiresDot: [dotValue boolValue]];
|
||||||
|
|
||||||
#warning this domain code has no effect yet
|
#warning this domain code has no effect yet
|
||||||
if ([sourceDomain length])
|
if ([sourceDomain length])
|
||||||
ASSIGN (_domain, sourceDomain);
|
ASSIGN (_domain, sourceDomain);
|
||||||
|
@ -183,7 +193,7 @@
|
||||||
{
|
{
|
||||||
NSString *pass;
|
NSString *pass;
|
||||||
NSString* result;
|
NSString* result;
|
||||||
|
|
||||||
pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
|
pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
|
||||||
|
|
||||||
if (pass == nil)
|
if (pass == nil)
|
||||||
|
@ -264,7 +274,7 @@
|
||||||
@" WHERE ",
|
@" WHERE ",
|
||||||
[_viewURL gcsTableName]];
|
[_viewURL gcsTableName]];
|
||||||
if (_authenticationFilter)
|
if (_authenticationFilter)
|
||||||
{
|
{
|
||||||
qualifier = [[EOAndQualifier alloc] initWithQualifiers:
|
qualifier = [[EOAndQualifier alloc] initWithQualifiers:
|
||||||
qualifier,
|
qualifier,
|
||||||
[EOQualifier qualifierWithQualifierFormat: _authenticationFilter],
|
[EOQualifier qualifierWithQualifierFormat: _authenticationFilter],
|
||||||
|
@ -272,18 +282,18 @@
|
||||||
[qualifier autorelease];
|
[qualifier autorelease];
|
||||||
}
|
}
|
||||||
[qualifier _gcsAppendToString: sql];
|
[qualifier _gcsAppendToString: sql];
|
||||||
|
|
||||||
ex = [channel evaluateExpressionX: sql];
|
ex = [channel evaluateExpressionX: sql];
|
||||||
if (!ex)
|
if (!ex)
|
||||||
{
|
{
|
||||||
NSDictionary *row;
|
NSDictionary *row;
|
||||||
NSArray *attrs;
|
NSArray *attrs;
|
||||||
NSString *value;
|
NSString *value;
|
||||||
|
|
||||||
attrs = [channel describeResults: NO];
|
attrs = [channel describeResults: NO];
|
||||||
row = [channel fetchAttributes: attrs withZone: NULL];
|
row = [channel fetchAttributes: attrs withZone: NULL];
|
||||||
value = [row objectForKey: @"c_password"];
|
value = [row objectForKey: @"c_password"];
|
||||||
|
|
||||||
rc = [self _isPassword: _pwd equalTo: value];
|
rc = [self _isPassword: _pwd equalTo: value];
|
||||||
[channel cancelFetch];
|
[channel cancelFetch];
|
||||||
}
|
}
|
||||||
|
@ -308,9 +318,9 @@
|
||||||
* @return YES if the password was successfully changed.
|
* @return YES if the password was successfully changed.
|
||||||
*/
|
*/
|
||||||
- (BOOL) changePasswordForLogin: (NSString *) login
|
- (BOOL) changePasswordForLogin: (NSString *) login
|
||||||
oldPassword: (NSString *) oldPassword
|
oldPassword: (NSString *) oldPassword
|
||||||
newPassword: (NSString *) newPassword
|
newPassword: (NSString *) newPassword
|
||||||
perr: (SOGoPasswordPolicyError *) perr
|
perr: (SOGoPasswordPolicyError *) perr
|
||||||
{
|
{
|
||||||
EOAdaptorChannel *channel;
|
EOAdaptorChannel *channel;
|
||||||
GCSChannelManager *cm;
|
GCSChannelManager *cm;
|
||||||
|
@ -339,10 +349,10 @@
|
||||||
if (channel)
|
if (channel)
|
||||||
{
|
{
|
||||||
sqlstr = [NSString stringWithFormat: (@"UPDATE %@"
|
sqlstr = [NSString stringWithFormat: (@"UPDATE %@"
|
||||||
@" SET c_password = '%@'"
|
@" SET c_password = '%@'"
|
||||||
@" WHERE c_uid = '%@'"),
|
@" WHERE c_uid = '%@'"),
|
||||||
[_viewURL gcsTableName], encryptedPassword, login];
|
[_viewURL gcsTableName], encryptedPassword, login];
|
||||||
|
|
||||||
ex = [channel evaluateExpressionX: sqlstr];
|
ex = [channel evaluateExpressionX: sqlstr];
|
||||||
if (!ex)
|
if (!ex)
|
||||||
{
|
{
|
||||||
|
@ -355,13 +365,13 @@
|
||||||
[cm releaseChannel: channel];
|
[cm releaseChannel: channel];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return didChange;
|
return didChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *) _whereClauseFromArray: (NSArray *) theArray
|
- (NSString *) _whereClauseFromArray: (NSArray *) theArray
|
||||||
value: (NSString *) theValue
|
value: (NSString *) theValue
|
||||||
exact: (BOOL) theBOOL
|
exact: (BOOL) theBOOL
|
||||||
{
|
{
|
||||||
NSMutableString *s;
|
NSMutableString *s;
|
||||||
int i;
|
int i;
|
||||||
|
@ -379,6 +389,35 @@
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) _fillConstraintsForModule: (NSString *) module
|
||||||
|
intoRecord: (NSMutableDictionary *) record
|
||||||
|
{
|
||||||
|
NSDictionary *constraints;
|
||||||
|
NSEnumerator *matches;
|
||||||
|
NSString *currentMatch, *currentValue, *recordValue;
|
||||||
|
BOOL result;
|
||||||
|
|
||||||
|
result = YES;
|
||||||
|
|
||||||
|
constraints = [_modulesConstraints objectForKey: module];
|
||||||
|
if (constraints)
|
||||||
|
{
|
||||||
|
matches = [[constraints allKeys] objectEnumerator];
|
||||||
|
while (result == YES && (currentMatch = [matches nextObject]))
|
||||||
|
{
|
||||||
|
currentValue = [constraints objectForKey: currentMatch];
|
||||||
|
recordValue = [record objectForKey: currentMatch];
|
||||||
|
result = NO;
|
||||||
|
|
||||||
|
if ([recordValue caseInsensitiveMatches: currentValue])
|
||||||
|
result = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[record setObject: [NSNumber numberWithBool: result]
|
||||||
|
forKey: [NSString stringWithFormat: @"%@Access", module]];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSDictionary *) _lookupContactEntry: (NSString *) theID
|
- (NSDictionary *) _lookupContactEntry: (NSString *) theID
|
||||||
considerEmail: (BOOL) b
|
considerEmail: (BOOL) b
|
||||||
inDomain: (NSString *) domain
|
inDomain: (NSString *) domain
|
||||||
|
@ -425,7 +464,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
domainQualifier = nil;
|
domainQualifier = nil;
|
||||||
if (_domainField && domain)
|
if (_domainField && domain)
|
||||||
{
|
{
|
||||||
|
@ -443,7 +482,7 @@
|
||||||
value: [theID lowercaseString]];
|
value: [theID lowercaseString]];
|
||||||
[loginQualifier autorelease];
|
[loginQualifier autorelease];
|
||||||
[qualifiers addObject: loginQualifier];
|
[qualifiers addObject: loginQualifier];
|
||||||
|
|
||||||
if (_mailFields)
|
if (_mailFields)
|
||||||
{
|
{
|
||||||
for (i = 0; i < [_mailFields count]; i++)
|
for (i = 0; i < [_mailFields count]; i++)
|
||||||
|
@ -461,7 +500,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sql = [NSMutableString stringWithFormat: @"SELECT *"
|
sql = [NSMutableString stringWithFormat: @"SELECT *"
|
||||||
@" FROM %@"
|
@" FROM %@"
|
||||||
@" WHERE ",
|
@" WHERE ",
|
||||||
|
@ -491,12 +530,9 @@
|
||||||
forKey: [field substringFromIndex: 2]];
|
forKey: [field substringFromIndex: 2]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
[self _fillConstraintsForModule: @"Calendar" intoRecord: response];
|
||||||
// We have to do this here since we do not manage modules
|
[self _fillConstraintsForModule: @"Mail" intoRecord: response];
|
||||||
// constraints right now over a SQL backend.
|
[self _fillConstraintsForModule: @"ActiveSync" intoRecord: response];
|
||||||
[response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"];
|
|
||||||
[response setObject: [NSNumber numberWithBool: YES] forKey: @"MailAccess"];
|
|
||||||
[response setObject: [NSNumber numberWithBool: YES] forKey: @"ActiveSyncAccess"];
|
|
||||||
|
|
||||||
// We set the domain, if any
|
// We set the domain, if any
|
||||||
value = nil;
|
value = nil;
|
||||||
|
@ -510,7 +546,7 @@
|
||||||
|
|
||||||
// We populate all mail fields
|
// We populate all mail fields
|
||||||
emails = [NSMutableArray array];
|
emails = [NSMutableArray array];
|
||||||
|
|
||||||
if ([response objectForKey: @"mail"])
|
if ([response objectForKey: @"mail"])
|
||||||
[emails addObject: [response objectForKey: @"mail"]];
|
[emails addObject: [response objectForKey: @"mail"]];
|
||||||
|
|
||||||
|
@ -520,12 +556,12 @@
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < [_mailFields count]; i++)
|
for (i = 0; i < [_mailFields count]; i++)
|
||||||
if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) &&
|
if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) &&
|
||||||
[s isNotNull] &&
|
[s isNotNull] &&
|
||||||
[[s stringByTrimmingSpaces] length] > 0)
|
[[s stringByTrimmingSpaces] length] > 0)
|
||||||
[emails addObjectsFromArray: [s componentsSeparatedByString: @" "]];
|
[emails addObjectsFromArray: [s componentsSeparatedByString: @" "]];
|
||||||
}
|
}
|
||||||
|
|
||||||
[response setObject: emails forKey: @"c_emails"];
|
[response setObject: emails forKey: @"c_emails"];
|
||||||
if (_imapHostField)
|
if (_imapHostField)
|
||||||
{
|
{
|
||||||
|
@ -576,7 +612,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
[response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"];
|
[response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"];
|
||||||
|
|
||||||
// We check if we should use a different login for IMAP
|
// We check if we should use a different login for IMAP
|
||||||
if (_imapLoginField)
|
if (_imapLoginField)
|
||||||
{
|
{
|
||||||
|
@ -586,26 +622,26 @@
|
||||||
|
|
||||||
// We check if it's a resource of not
|
// We check if it's a resource of not
|
||||||
if (_kindField)
|
if (_kindField)
|
||||||
{
|
{
|
||||||
if ((value = [response objectForKey: _kindField]) && [value isNotNull])
|
if ((value = [response objectForKey: _kindField]) && [value isNotNull])
|
||||||
{
|
{
|
||||||
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
|
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
|
||||||
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
|
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
|
||||||
[value caseInsensitiveCompare: @"group"] == NSOrderedSame)
|
[value caseInsensitiveCompare: @"group"] == NSOrderedSame)
|
||||||
{
|
{
|
||||||
[response setObject: [NSNumber numberWithInt: 1]
|
[response setObject: [NSNumber numberWithInt: 1]
|
||||||
forKey: @"isResource"];
|
forKey: @"isResource"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_multipleBookingsField)
|
if (_multipleBookingsField)
|
||||||
{
|
{
|
||||||
if ((value = [response objectForKey: _multipleBookingsField]))
|
if ((value = [response objectForKey: _multipleBookingsField]))
|
||||||
{
|
{
|
||||||
[response setObject: [NSNumber numberWithInt: [value intValue]]
|
[response setObject: [NSNumber numberWithInt: [value intValue]]
|
||||||
forKey: @"numberOfSimultaneousBookings"];
|
forKey: @"numberOfSimultaneousBookings"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[response setObject: self forKey: @"source"];
|
[response setObject: self forKey: @"source"];
|
||||||
|
@ -730,7 +766,7 @@
|
||||||
NSString *value;
|
NSString *value;
|
||||||
|
|
||||||
attrs = [channel describeResults: NO];
|
attrs = [channel describeResults: NO];
|
||||||
|
|
||||||
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
|
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
|
||||||
{
|
{
|
||||||
value = [row objectForKey: @"c_uid"];
|
value = [row objectForKey: @"c_uid"];
|
||||||
|
@ -746,7 +782,7 @@
|
||||||
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
||||||
[_viewURL absoluteString]];
|
[_viewURL absoluteString]];
|
||||||
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -764,73 +800,76 @@
|
||||||
NSException *ex;
|
NSException *ex;
|
||||||
NSMutableString *sql;
|
NSMutableString *sql;
|
||||||
NSString *lowerFilter;
|
NSString *lowerFilter;
|
||||||
|
|
||||||
results = [NSMutableArray array];
|
results = [NSMutableArray array];
|
||||||
|
|
||||||
cm = [GCSChannelManager defaultChannelManager];
|
if ([filter length] > 0 || !_listRequiresDot)
|
||||||
channel = [cm acquireOpenChannelForURL: _viewURL];
|
|
||||||
if (channel)
|
|
||||||
{
|
{
|
||||||
lowerFilter = [filter lowercaseString];
|
cm = [GCSChannelManager defaultChannelManager];
|
||||||
lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"];
|
channel = [cm acquireOpenChannelForURL: _viewURL];
|
||||||
|
if (channel)
|
||||||
sql = [NSMutableString stringWithFormat: (@"SELECT *"
|
|
||||||
@" FROM %@"
|
|
||||||
@" WHERE"
|
|
||||||
@" (LOWER(c_cn) LIKE '%%%@%%'"
|
|
||||||
@" OR LOWER(mail) LIKE '%%%@%%'"),
|
|
||||||
[_viewURL gcsTableName],
|
|
||||||
lowerFilter, lowerFilter];
|
|
||||||
|
|
||||||
if (_mailFields && [_mailFields count] > 0)
|
|
||||||
{
|
{
|
||||||
[sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]];
|
lowerFilter = [filter lowercaseString];
|
||||||
}
|
lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"];
|
||||||
|
|
||||||
[sql appendString: @")"];
|
sql = [NSMutableString stringWithFormat: (@"SELECT *"
|
||||||
|
@" FROM %@"
|
||||||
|
@" WHERE"
|
||||||
|
@" (LOWER(c_cn) LIKE '%%%@%%'"
|
||||||
|
@" OR LOWER(mail) LIKE '%%%@%%'"),
|
||||||
|
[_viewURL gcsTableName],
|
||||||
|
lowerFilter, lowerFilter];
|
||||||
|
|
||||||
if (_domainField)
|
if (_mailFields && [_mailFields count] > 0)
|
||||||
{
|
|
||||||
if ([domain length])
|
|
||||||
{
|
{
|
||||||
EOQualifier *domainQualifier;
|
[sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]];
|
||||||
domainQualifier =
|
}
|
||||||
[self _visibleDomainsQualifierFromDomain: domain];
|
|
||||||
if (domainQualifier)
|
[sql appendString: @")"];
|
||||||
|
|
||||||
|
if (_domainField)
|
||||||
|
{
|
||||||
|
if ([domain length])
|
||||||
{
|
{
|
||||||
[sql appendFormat: @" AND ("];
|
EOQualifier *domainQualifier;
|
||||||
[domainQualifier _gcsAppendToString: sql];
|
domainQualifier =
|
||||||
[sql appendFormat: @")"];
|
[self _visibleDomainsQualifierFromDomain: domain];
|
||||||
|
if (domainQualifier)
|
||||||
|
{
|
||||||
|
[sql appendFormat: @" AND ("];
|
||||||
|
[domainQualifier _gcsAppendToString: sql];
|
||||||
|
[sql appendFormat: @")"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
[sql appendFormat: @" AND %@ IS NULL", _domainField];
|
||||||
|
}
|
||||||
|
|
||||||
|
ex = [channel evaluateExpressionX: sql];
|
||||||
|
if (!ex)
|
||||||
|
{
|
||||||
|
NSDictionary *row;
|
||||||
|
NSArray *attrs;
|
||||||
|
|
||||||
|
attrs = [channel describeResults: NO];
|
||||||
|
|
||||||
|
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
|
||||||
|
{
|
||||||
|
row = [row mutableCopy];
|
||||||
|
[(NSMutableDictionary *) row setObject: self forKey: @"source"];
|
||||||
|
[results addObject: row];
|
||||||
|
[row release];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
[sql appendFormat: @" AND %@ IS NULL", _domainField];
|
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
||||||
}
|
[cm releaseChannel: channel];
|
||||||
|
|
||||||
ex = [channel evaluateExpressionX: sql];
|
|
||||||
if (!ex)
|
|
||||||
{
|
|
||||||
NSDictionary *row;
|
|
||||||
NSArray *attrs;
|
|
||||||
|
|
||||||
attrs = [channel describeResults: NO];
|
|
||||||
|
|
||||||
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
|
|
||||||
{
|
|
||||||
row = [row mutableCopy];
|
|
||||||
[(NSMutableDictionary *) row setObject: self forKey: @"source"];
|
|
||||||
[results addObject: row];
|
|
||||||
[row release];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
||||||
[cm releaseChannel: channel];
|
[_viewURL absoluteString]];
|
||||||
}
|
}
|
||||||
else
|
|
||||||
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
|
||||||
[_viewURL absoluteString]];
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,13 +895,12 @@
|
||||||
|
|
||||||
- (void) setListRequiresDot: (BOOL) newListRequiresDot
|
- (void) setListRequiresDot: (BOOL) newListRequiresDot
|
||||||
{
|
{
|
||||||
|
_listRequiresDot = newListRequiresDot;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) listRequiresDot
|
- (BOOL) listRequiresDot
|
||||||
{
|
{
|
||||||
/* This method is not implemented for SQLSource. It must enable a mechanism
|
return _listRequiresDot;
|
||||||
where using "." is not required to list the content of addressbooks. */
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* card editing */
|
/* card editing */
|
||||||
|
|
Loading…
Reference in New Issue