CFDBFilterParser

Used to parse boolean expression strings like 'field1=value1&&field2=value2||field3=value3&&field4=value4' Where logical AND and OR are represented by && and || respectively.

Defined (1)

The class is defined in the following location(s).

/CFDBFilterParser.php  
  1. class CFDBFilterParser extends CFDBParserBase implements CFDBEvaluator { 
  2.  
  3. /** 
  4. * @var array of arrays of string where the top level array is broken down on the || delimiters 
  5. */ 
  6. var $tree; 
  7.  
  8.  
  9. public function hasFilters() { 
  10. return count($this->tree) > 0; // count is null-safe 
  11.  
  12. public function getFilterTree() { 
  13. return $this->tree; 
  14.  
  15. /** 
  16. * Parse a string with delimiters || and/or && into a Boolean evaluation tree. 
  17. * For example: aaa&&bbb||ccc&&ddd would be parsed into the following tree,  
  18. * where level 1 represents items ORed, level 2 represents items ANDed, and 
  19. * level 3 represent individual expressions. 
  20. * Array 
  21. * ( 
  22. * [0] => Array 
  23. * ( 
  24. * [0] => Array 
  25. * ( 
  26. * [0] => aaa 
  27. * [1] => = 
  28. * [2] => bbb 
  29. * ) 
  30. * ) 
  31. * [1] => Array 
  32. * ( 
  33. * [0] => Array 
  34. * ( 
  35. * [0] => ccc 
  36. * [1] => = 
  37. * [2] => ddd 
  38. * ) 
  39. * [1] => Array 
  40. * ( 
  41. * [0] => eee 
  42. * [1] => = 
  43. * [2] => fff 
  44. * ) 
  45. * ) 
  46. * ) 
  47. * @param $filterString string with delimiters && and/or || 
  48. * which each element being an array of strings broken on the && delimiter 
  49. */ 
  50. public function parse($filterString) { 
  51. $this->tree = array(); 
  52. $arrayOfORedStrings = $this->parseORs($filterString); 
  53. foreach ($arrayOfORedStrings as $anANDString) { 
  54. $arrayOfANDedStrings = $this->parseANDs($anANDString); 
  55. $andSubTree = array(); 
  56. foreach ($arrayOfANDedStrings as $anExpressionString) { 
  57. $exprArray = $this->parseExpression($anExpressionString); 
  58. $count = count($exprArray); 
  59. if ($count > 0) { 
  60. $exprArray[0] = $this->parseValidFunction($exprArray[0]); 
  61. if ($count > 2) { 
  62. $exprArray[2] = $this->parseValidFunction($exprArray[2]); 
  63. } else if ($count > 1) { 
  64. // e.g. "field=" which can happen if it was "field=$_POST(field)" with no $_POST['field'] set 
  65. $exprArray[2] = null; 
  66. } else { 
  67. // Case of "function()" parse as if "function()==true" 
  68. $exprArray[1] = '=='; 
  69. $exprArray[2] = true; 
  70.  
  71. // if one side of the operation is a function and the other is 'true' or 'false' 
  72. // then convert to Boolean true or false which signals to not try to dereference 
  73. // true or false during evaluateComparison() 
  74. if (is_array($exprArray[0])) { 
  75. if ($exprArray[2] === 'true') { 
  76. $exprArray[2] = true; 
  77. } else if ($exprArray[2] === 'false') { 
  78. $exprArray[2] = false; 
  79. if (is_array($exprArray[2])) { 
  80. if ($exprArray[0] === 'true') { 
  81. $exprArray[0] = true; 
  82. if ($exprArray[0] === 'false') { 
  83. $exprArray[0] = false; 
  84. $andSubTree[] = $exprArray; 
  85. $this->tree[] = $andSubTree; 
  86.  
  87. /** 
  88. * Parse a comparison expression into its three components 
  89. * @param $comparisonExpression string in the form 'value1' . 'operator' . 'value2' where 
  90. * operator is a php comparison operator or '=' 
  91. * @return array of string [ value1, operator, value2 ] 
  92. */ 
  93. public function parseExpression($comparisonExpression) { 
  94. // Sometimes get HTML codes for greater-than and less-than; replace them with actual symbols 
  95. $comparisonExpression = str_replace('>', '>', $comparisonExpression); 
  96. $comparisonExpression = str_replace('<', '<', $comparisonExpression); 
  97. return preg_split('/(===)|(==)|(=)|(!==)|(!=)|(<>)|(<=)|(<)|(>=)|(>)|(~~)/',  
  98. $comparisonExpression, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); 
  99.  
  100. /** 
  101. * Evaluate expression against input data. Assumes parse was called to set up the expression to 
  102. * evaluate. Expression should have key . operator . value tuples and input $data should have the same keys 
  103. * with values to check against them. 
  104. * For example, an expression in this object is 'name=john' and the input data has [ 'name' => 'john' ]. In 
  105. * this case true is returned. if $data has [ 'name' => 'fred' ] then false is returned. 
  106. * @param $data array [ key => value] 
  107. * @return boolean result of evaluating $data against expression tree 
  108. */ 
  109. public function evaluate(&$data) { 
  110. $this->setTimezone(); 
  111.  
  112. $retVal = true; 
  113. if ($this->tree) { 
  114. $retVal = false; 
  115. foreach ($this->tree as $andArray) { // loop each OR'ed $andArray 
  116. $andBoolean = true; 
  117. // evaluation the list of AND'ed comparison expressions 
  118. foreach ($andArray as $comparison) { 
  119. $andBoolean = $this->evaluateComparison($comparison, $data); //&& $andBoolean 
  120. if (!$andBoolean) { 
  121. break; // short-circuit AND expression evaluation 
  122. $retVal = $retVal || $andBoolean; 
  123. if ($retVal) { 
  124. break; // short-circuit OR expression evaluation 
  125. return $retVal; 
  126.  
  127. public function evaluateComparison($andExpr, &$data) { 
  128. if (is_array($andExpr) && count($andExpr) == 3) { 
  129. // $andExpr = [$left $op $right] 
  130.  
  131. // Left operand 
  132. $left = $andExpr[0]; 
  133. // Boolean type means it was set in parse in response 
  134. // to a filter like "function(x)" that was turned into an expression 
  135. // like "function(x) === true" 
  136. if ($left !== true && $left !== false) { 
  137. if (is_array($left)) { // function call 
  138. $left = $this->functionEvaluator->evaluateFunction($left, $data); 
  139. } else { 
  140. $left = $this->functionEvaluator->preprocessValues($left); 
  141. // Dereference $left assuming it is the name of a form field 
  142. // and set it to the value of the field. When not found make it null 
  143. $left = isset($data[$left]) ? $data[$left] : null; 
  144.  
  145. // Operator 
  146. $op = $andExpr[1]; 
  147.  
  148. // Right operand 
  149. $right = $andExpr[2]; 
  150. if (is_array($right)) { // function call 
  151. $right = $this->functionEvaluator->evaluateFunction($right, $data); 
  152. } else { 
  153. $right = $this->functionEvaluator->preprocessValues($right); 
  154.  
  155. if ($andExpr[0] === 'submit_time') { 
  156. if (!is_numeric($right)) { 
  157. $right = strtotime($right); 
  158.  
  159. if ($left === null && $right === null) { 
  160. // Addresses case where 'Submitted Login' = $user_login but there exist some submissions 
  161. // with no 'Submitted Login' field. Without this clause, those rows where 'Submitted Login' == null 
  162. // would be returned when what we really want to is affirm that there is a 'Submitted Login' value ($left) 
  163. // But we want to preserve the correct behavior for the case where 'field'=null is the constraint. 
  164. return false; 
  165. return $this->evaluateLeftOpRightComparison($left, $op, $right); 
  166. return false; 
  167.  
  168. /** 
  169. * @param $left mixed 
  170. * @param $operator string representing any PHP comparison operator or '=' which is taken to mean '==' 
  171. * @param $right $mixed. SPECIAL CASE: if it is the string 'null' it is taken to be the value null 
  172. * @return bool evaluation of comparison $left $operator $right 
  173. */ 
  174. public function evaluateLeftOpRightComparison($left, $operator, $right) { 
  175. if ($right === 'null') { 
  176. // special case 
  177. $right = null; 
  178.  
  179. // Try to do numeric comparisons when possible 
  180. if (is_numeric($left) && is_numeric($right)) { 
  181. $left = (float)$left; 
  182. $right = (float)$right; 
  183.  
  184. // Could do this easier with eval() but since this text ultimately 
  185. // comes form a shortcode's user-entered attributes, I want to avoid a security hole 
  186. $retVal = false; 
  187. switch ($operator) { 
  188. case '=' : 
  189. case '==': 
  190. $retVal = $left == $right; 
  191. break; 
  192.  
  193. case '===': 
  194. $retVal = $left === $right; 
  195. break; 
  196.  
  197. case '!=': 
  198. $retVal = $left != $right; 
  199. break; 
  200.  
  201. case '!==': 
  202. $retVal = $left !== $right; 
  203. break; 
  204.  
  205. case '<>': 
  206. $retVal = $left <> $right; 
  207. break; 
  208.  
  209. case '>': 
  210. $retVal = $left > $right; 
  211. break; 
  212.  
  213. case '>=': 
  214. $retVal = $left >= $right; 
  215. break; 
  216.  
  217. case '<': 
  218. $retVal = $left < $right; 
  219. break; 
  220.  
  221. case '<=': 
  222. $retVal = $left <= $right; 
  223. break; 
  224.  
  225. case '~~': 
  226. $retVal = @preg_match($right, $left) > 0; 
  227. break; 
  228.  
  229. default: 
  230. trigger_error("Invalid operator: '$operator'", E_USER_NOTICE); 
  231. break; 
  232.  
  233. return $retVal; 
  234.