Skip to content

Commit 30663a1

Browse files
committed
Fix LdapSource read queries to properly handle conversion of conditions array to LDAP filter, to support offsets, and to properly handle counts (though still a hack)
1 parent 47c4672 commit 30663a1

1 file changed

Lines changed: 83 additions & 101 deletions

File tree

Model/Datasource/LdapSource.php

Lines changed: 83 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,12 @@ public function read(Model $model, $queryData = array(), $recursive = null) {
425425

426426
// Check if we are doing a 'count' .. this is kinda ugly but i couldn't find a better way to do this, yet
427427
if (is_string($queryData['fields']) && $queryData['fields'] === 'COUNT(*) AS ' . $this->name('count')) {
428-
$queryData['fields'] = array();
428+
// limit a count request to a minimum field set
429+
$queryData['fields'] = array($model->primaryKey);
429430
}
430431

431432
// Prepare query data ------------------------
432-
$queryData['conditions'] = $this->_conditions($queryData['conditions'], $model);
433+
$queryData['conditions'] = $this->_conditionsArrayToString($queryData['conditions'], $model);
433434
if (empty($queryData['targetDn'])) {
434435
$queryData['targetDn'] = $model->useTable;
435436
}
@@ -459,6 +460,11 @@ public function read(Model $model, $queryData = array(), $recursive = null) {
459460
$resultSet = ldap_get_entries($this->database, $res);
460461
$resultSet = $this->_ldapFormat($model, $resultSet);
461462

463+
// If an offset was specified, apply it now
464+
if ($queryData['offset']) {
465+
$resultSet = array_slice($resultSet, $queryData['offset']);
466+
$this->count = count($resultSet);
467+
}
462468
// Query on linked models ----------------------
463469
if ($model->recursive > 0) {
464470
foreach ($model->_associations as $type) {
@@ -487,7 +493,12 @@ public function read(Model $model, $queryData = array(), $recursive = null) {
487493
}
488494

489495
// Add the count field to the resultSet (needed by find() to work out how many entries we got back .. used when $model->exists() is called)
490-
$resultSet[0][0]['count'] = $this->lastNumRows();
496+
if ($queryData['offset']) {
497+
// if an offset was applied, net count instead of the actual count
498+
$resultSet[0][0]['count'] = $this->count;
499+
} else {
500+
$resultSet[0][0]['count'] = $this->lastNumRows();
501+
}
491502
return $resultSet;
492503
}
493504

@@ -1014,101 +1025,80 @@ public function showQuery($query) {
10141025
}
10151026
}
10161027

1017-
protected function _conditions($conditions, $model) {
1018-
$res = '';
1019-
$key = $model->primaryKey;
1020-
$name = $model->name;
1021-
1022-
if (is_array($conditions) && count($conditions) === 1) {
1023-
$sqlHack = "$name.$key";
1024-
$conditions = str_ireplace($sqlHack, $key, $conditions);
1025-
foreach ($conditions as $k => $v) {
1026-
if ($k === $name . '.dn') {
1027-
$res = substr($v, 0, strpos($v, ','));
1028-
} elseif (($k === $sqlHack) && (empty($v) || $v === '*')) {
1029-
$res = 'objectclass=*';
1030-
} elseif ($k === $sqlHack) {
1031-
$res = "$key=$v";
1032-
} else {
1033-
$res = "$k=$v";
1034-
}
1035-
}
1036-
$conditions = $res;
1037-
}
1038-
1039-
if (is_array($conditions)) {
1040-
// Conditions expressed as an array
1041-
if (empty($conditions)) {
1042-
$res = 'objectclass=*';
1043-
}
1044-
}
1045-
1046-
if (empty($conditions)) {
1047-
$res = 'objectclass=*';
1048-
} else {
1049-
$res = $conditions;
1050-
}
1051-
return $res;
1052-
}
1053-
10541028
/**
10551029
* Convert an array into a ldap condition string
10561030
*
1057-
* @param array $conditions condition
1031+
* @param array $conditions Condition
1032+
* @param object $model Model
1033+
* @param string $join The type of opeation to use to join the conditions (&, |, !)
10581034
* @return string
10591035
*/
1060-
protected function _conditionsArrayToString($conditions) {
1061-
$opsRec = array('and' => array('prefix' => '&'), 'or' => array('prefix' => '|'));
1062-
$opsNeg = array('and not' => array(), 'or not' => array(), 'not equals' => array());
1063-
$opsTer = array('equals' => array('null' => '*'));
1064-
1065-
$ops = array_merge($opsRec, $opsNeg, $opsTer);
1066-
1036+
protected function _conditionsArrayToString($conditions, $model, $join = '&') {
10671037
if (is_array($conditions)) {
1068-
$operand = array_keys($conditions);
1069-
$operand = $operand[0];
1070-
1071-
if (!in_array($operand, array_keys($ops))) {
1072-
$this->log("No operators defined in LDAP search conditions.", 'ldap.error');
1073-
return null;
1074-
}
1075-
1076-
$children = $conditions[$operand];
1077-
1078-
if (in_array($operand, array_keys($opsRec))) {
1079-
if (!is_array($children)) {
1080-
return null;
1081-
}
1082-
$tmp = '(' . $opsRec[$operand]['prefix'];
1083-
foreach ($children as $key => $value) {
1084-
$child = array($key => $value);
1085-
$tmp .= $this->_conditionsArrayToString($child);
1086-
}
1087-
return $tmp . ')';
1088-
}
1089-
1090-
if (in_array($operand, array_keys($opsNeg))) {
1091-
if (!is_array($children)) {
1092-
return null;
1093-
}
1094-
$nextOperand = trim(str_replace('not', '', $operand));
1095-
1096-
return '(!' . $this->_conditionsArrayToString(array($nextOperand => $children)) . ')';
1097-
}
1098-
1099-
if (in_array($operand, array_keys($opsTer))) {
1100-
$tmp = '';
1101-
foreach ($children as $key => $value) {
1102-
if (!is_array($value)) {
1103-
$tmp .= '(' . $key . '=' . ($value === null ? $opsTer['equals']['null'] : $value) . ')';
1104-
} else {
1105-
foreach ($value as $subvalue) {
1106-
$tmp .= $this->_conditionsArrayToString(array('equals' => array($key => $subvalue)));
1038+
// Process array of conditions
1039+
$ret_parts = array();
1040+
foreach ($conditions as $k => $v) {
1041+
// Check key for each component part
1042+
switch (strtoupper($k)) {
1043+
case 'OR':
1044+
if (is_array($v)) {
1045+
// the children of this condition will be processed with an OR join
1046+
$ret_parts[] = $this->_conditionsArrayToString($v, $model, '|');
1047+
} else {
1048+
// Single OR condition? This is probably an error.
1049+
$ret_parts[] = $this->_conditionsArrayToString($v, $model);
1050+
}
1051+
break;
1052+
case 'NOT':
1053+
// the childern of this condition will be processed with a NOT join
1054+
$ret_parts[] = $this->_conditionsArrayToString($v, $model, '!');
1055+
break;
1056+
case 'AND':
1057+
// the children of this condition will be processed with an AND join (default)
1058+
$ret_parts[] = $this->_conditionsArrayToString($v, $model);
1059+
break;
1060+
default:
1061+
// This is an array, but not an explicitly named boolean operation
1062+
if (is_numeric($k)) {
1063+
// numeric keys indicate a default AND
1064+
$ret_parts[] = $this->_conditionsArrayToString($v, $model);
1065+
} else {
1066+
// Remove SQL-like naming from keys
1067+
$k = str_ireplace($model->name.'.', '', $k);
1068+
if (strpos($k, '!=')) {
1069+
// A not equals must be converted to a NOT condition
1070+
$ret_parts[] = '(!('.rtrim(substr($k, 0, strpos($k, '!='))).'='.$v.'))';
1071+
} else if (preg_match('/([<>~]=)$/', $k, $op)) {
1072+
// A lexical greater-than/less-than/approximately can be passed directly
1073+
$ret_parts[] = '('.$k.$v.')';
1074+
} else if (is_array($v)) {
1075+
// An array of values is an IN statement. This must be converted to an OR join
1076+
$r = '(|';
1077+
foreach ($v as $i) {
1078+
$r .= '('.$k.'='.$i.')';
1079+
}
1080+
$ret_parts[] = $r.')';
1081+
} else if ($v === NULL) {
1082+
// A check for NULL must be converted to a NOT any match
1083+
$ret_parts[] = '(!('.$k.'=*))';
1084+
} else {
1085+
// A single key-value pair is an equality check
1086+
$ret_parts[] = '('.$k.'='.$v.')';
1087+
}
11071088
}
1108-
}
11091089
}
1110-
return $tmp;
11111090
}
1091+
// If there is only one part to this condition, return immediately
1092+
if (count($conditions) == 1) {
1093+
return $ret_parts[0];
1094+
}
1095+
// Otherwise paste the parts together with the join
1096+
return '('.$join.implode('', $ret_parts).')';
1097+
} else {
1098+
// Remove SQL-like naming from keys
1099+
$conditions = str_ireplace($model->name.'.', '', $conditions);
1100+
// Ensure string condition has leading and trailing parenthesis
1101+
return strpos($conditions, '(') === 0 ? (string) $conditions : '('.$conditions.')';
11121102
}
11131103
}
11141104

@@ -1164,7 +1154,8 @@ protected function _executeQuery($queryData = array(), $cache = true) {
11641154
if ($queryData['fields'] == 1) {
11651155
$queryData['fields'] = array();
11661156
}
1167-
$res = @ldap_search($this->database, $queryData['targetDn'], $queryData['conditions'], $queryData['fields'], 0, $queryData['limit']);
1157+
// if an offset was requested, grab $offset + $limit results. We'll select just the desired results later (in read())
1158+
$res = @ldap_search($this->database, $queryData['targetDn'], $queryData['conditions'], $queryData['fields'], 0, (isset($queryData['offset']) ? $queryData['offset'] : 0) + $queryData['limit']);
11681159
}
11691160

11701161
if (!$res) {
@@ -1387,19 +1378,10 @@ public function boolean() {
13871378
* @return integer Entry count
13881379
*/
13891380
public function calculate(Model $model, $func, $params = array()) {
1390-
$params = (array)$params;
1391-
13921381
switch (strtolower($func)) {
13931382
case 'count':
1394-
if (empty($params) && $model->id) {
1395-
// quick search to make sure it exsits
1396-
$queryData['targetDn'] = $model->id;
1397-
$queryData['conditions'] = 'objectClass=*';
1398-
$queryData['scope'] = 'base';
1399-
$query = $this->read($model, $queryData);
1400-
}
1401-
return $this->count;
1402-
1383+
// read() expects this magic string as a count indicator
1384+
return 'COUNT(*) AS ' . $this->name('count');
14031385
case 'max':
14041386
case 'min':
14051387
break;

0 commit comments

Comments
 (0)