Skip to content

Commit ab428f7

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 ab428f7

File tree

1 file changed

+81
-69
lines changed

1 file changed

+81
-69
lines changed

Model/Datasource/LdapSource.php

Lines changed: 81 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,8 @@ 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 ------------------------
@@ -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

@@ -1022,29 +1033,29 @@ protected function _conditions($conditions, $model) {
10221033
if (is_array($conditions) && count($conditions) === 1) {
10231034
$sqlHack = "$name.$key";
10241035
$conditions = str_ireplace($sqlHack, $key, $conditions);
1025-
foreach ($conditions as $k => $v) {
1036+
$k = array_pop(array_keys($conditions));
1037+
$v = $conditions[$k];
1038+
if (is_string($v)) {
10261039
if ($k === $name . '.dn') {
10271040
$res = substr($v, 0, strpos($v, ','));
10281041
} elseif (($k === $sqlHack) && (empty($v) || $v === '*')) {
1029-
$res = 'objectclass=*';
1042+
$res = $model->primaryKey.'=*';
10301043
} elseif ($k === $sqlHack) {
10311044
$res = "$key=$v";
10321045
} else {
10331046
$res = "$k=$v";
10341047
}
1048+
$conditions = $res;
10351049
}
1036-
$conditions = $res;
10371050
}
10381051

10391052
if (is_array($conditions)) {
10401053
// Conditions expressed as an array
1041-
if (empty($conditions)) {
1042-
$res = 'objectclass=*';
1043-
}
1054+
$conditions = $this->_conditionsArrayToString($conditions);
10441055
}
10451056

10461057
if (empty($conditions)) {
1047-
$res = 'objectclass=*';
1058+
$res = $model->primaryKey.'=*';
10481059
} else {
10491060
$res = $conditions;
10501061
}
@@ -1055,60 +1066,69 @@ protected function _conditions($conditions, $model) {
10551066
* Convert an array into a ldap condition string
10561067
*
10571068
* @param array $conditions condition
1069+
* @param string $join The type of opeation to use to join the conditions (&, |, !)
10581070
* @return string
10591071
*/
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-
1072+
protected function _conditionsArrayToString($conditions, $join = '&') {
10671073
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)));
1074+
// Process array of conditions
1075+
$ret_parts = array();
1076+
foreach ($conditions as $k => $v) {
1077+
// Check key for each component part
1078+
switch (strtoupper($k)) {
1079+
case 'OR':
1080+
if (is_array($v)) {
1081+
// the children of this condition will be processed with an OR join
1082+
$ret_parts[] = $this->_conditionsArrayToString($v, '|');
1083+
} else {
1084+
// Single OR condition? This is probably an error.
1085+
$ret_parts[] = $this->_conditionsArrayToString($v);
1086+
}
1087+
break;
1088+
case 'NOT':
1089+
// the childern of this condition will be processed with a NOT join
1090+
$ret_parts[] = $this->_conditionsArrayToString($v, '!');
1091+
break;
1092+
case 'AND':
1093+
// the children of this condition will be processed with an AND join (default)
1094+
$ret_parts[] = $this->_conditionsArrayToString($v);
1095+
break;
1096+
default:
1097+
// This is an array, but not an explicitly named boolean operation
1098+
if (is_numeric($k)) {
1099+
// numeric keys indicate a default AND
1100+
$ret_parts[] = $this->_conditionsArrayToString($v);
1101+
} else if (strpos($k, '!=')) {
1102+
// A not equals must be converted to a NOT condition
1103+
$ret_parts[] = '(!('.rtrim(substr($k, 0, strpos($k, '!='))).'='.$v.'))';
1104+
} else if (preg_match('/([<>~]=)$/', $k, $op)) {
1105+
// A lexical greater-than/less-than/approximately can be passed directly
1106+
$ret_parts[] = '('.$k.$v.')';
1107+
} else if (is_array($v)) {
1108+
// An array of values is an IN statement. This must be converted to an OR join
1109+
$r = '(|';
1110+
foreach ($v as $i) {
1111+
$r .= '('.$k.'='.$i.')';
1112+
}
1113+
$ret_parts[] = $r.')';
1114+
} else if ($v === NULL) {
1115+
// A check for NULL must be converted to a NOT any match
1116+
$ret_parts[] = '(!('.$k.'=*))';
1117+
} else {
1118+
// A single key-value pair is an equality check
1119+
$ret_parts[] = '('.$k.'='.$v.')';
11071120
}
1108-
}
11091121
}
1110-
return $tmp;
11111122
}
1123+
// If there is only one part to this condition, return immediately
1124+
if (count($conditions) == 1) {
1125+
return $ret_parts[0];
1126+
}
1127+
// Otherwise paste the parts together with the join
1128+
return '('.$join.implode('', $ret_parts).')';
1129+
} else {
1130+
// Ensure string condition has leading and trailing parenthesis
1131+
return strpos($conditions, '(') === 0 ? (string) $conditions : '('.$conditions.')';
11121132
}
11131133
}
11141134

@@ -1164,7 +1184,8 @@ protected function _executeQuery($queryData = array(), $cache = true) {
11641184
if ($queryData['fields'] == 1) {
11651185
$queryData['fields'] = array();
11661186
}
1167-
$res = @ldap_search($this->database, $queryData['targetDn'], $queryData['conditions'], $queryData['fields'], 0, $queryData['limit']);
1187+
// if an offset was requested, grab $offset + $limit results. We'll select just the desired results later (in read())
1188+
$res = @ldap_search($this->database, $queryData['targetDn'], $queryData['conditions'], $queryData['fields'], 0, (isset($queryData['offset']) ? $queryData['offset'] : 0) + $queryData['limit']);
11681189
}
11691190

11701191
if (!$res) {
@@ -1387,19 +1408,10 @@ public function boolean() {
13871408
* @return integer Entry count
13881409
*/
13891410
public function calculate(Model $model, $func, $params = array()) {
1390-
$params = (array)$params;
1391-
13921411
switch (strtolower($func)) {
13931412
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-
1413+
// read() expects this magic string as a count indicator
1414+
return 'COUNT(*) AS ' . $this->name('count');
14031415
case 'max':
14041416
case 'min':
14051417
break;

0 commit comments

Comments
 (0)