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