PageRenderTime 85ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/contact-form-7-to-database-extension/CFDBFilterParser.php

https://gitlab.com/mattswann/launch-housing
PHP | 302 lines | 156 code | 32 blank | 114 comment | 35 complexity | 91155a739f322f6800e7eb9b017a6c11 MD5 | raw file
  1. <?php
  2. /*
  3. "Contact Form to Database" Copyright (C) 2011-2012 Michael Simpson (email : michael.d.simpson@gmail.com)
  4. This file is part of Contact Form to Database.
  5. Contact Form to Database is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. Contact Form to Database is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with Contact Form to Database.
  15. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. include_once('CFDBEvaluator.php');
  18. require_once('CFDBParserBase.php');
  19. /**
  20. * Used to parse boolean expression strings like 'field1=value1&&field2=value2||field3=value3&&field4=value4'
  21. * Where logical AND and OR are represented by && and || respectively.
  22. * Individual expressions (like 'field1=value1') are of the form $name . $operator . $value where
  23. * $operator is any PHP comparison operator or '=' which is interpreted as '=='.
  24. * $value has a special case where if it is 'null' it is interpreted as the value null
  25. */
  26. class CFDBFilterParser extends CFDBParserBase implements CFDBEvaluator {
  27. /**
  28. * @var array of arrays of string where the top level array is broken down on the || delimiters
  29. */
  30. var $tree;
  31. public function hasFilters() {
  32. return count($this->tree) > 0; // count is null-safe
  33. }
  34. public function getFilterTree() {
  35. return $this->tree;
  36. }
  37. /**
  38. * Parse a string with delimiters || and/or && into a Boolean evaluation tree.
  39. * For example: aaa&&bbb||ccc&&ddd would be parsed into the following tree,
  40. * where level 1 represents items ORed, level 2 represents items ANDed, and
  41. * level 3 represent individual expressions.
  42. * Array
  43. * (
  44. * [0] => Array
  45. * (
  46. * [0] => Array
  47. * (
  48. * [0] => aaa
  49. * [1] => =
  50. * [2] => bbb
  51. * )
  52. *
  53. * )
  54. *
  55. * [1] => Array
  56. * (
  57. * [0] => Array
  58. * (
  59. * [0] => ccc
  60. * [1] => =
  61. * [2] => ddd
  62. * )
  63. *
  64. * [1] => Array
  65. * (
  66. * [0] => eee
  67. * [1] => =
  68. * [2] => fff
  69. * )
  70. *
  71. * )
  72. *
  73. * )
  74. * @param $filterString string with delimiters && and/or ||
  75. * which each element being an array of strings broken on the && delimiter
  76. */
  77. public function parse($filterString) {
  78. $this->tree = array();
  79. $arrayOfORedStrings = $this->parseORs($filterString);
  80. foreach ($arrayOfORedStrings as $anANDString) {
  81. $arrayOfANDedStrings = $this->parseANDs($anANDString);
  82. $andSubTree = array();
  83. foreach ($arrayOfANDedStrings as $anExpressionString) {
  84. $exprArray = $this->parseExpression($anExpressionString);
  85. $count = count($exprArray);
  86. if ($count > 0) {
  87. $exprArray[0] = $this->parseValidFunction($exprArray[0]);
  88. if ($count > 2) {
  89. $exprArray[2] = $this->parseValidFunction($exprArray[2]);
  90. } else if ($count > 1) {
  91. // e.g. "field=" which can happen if it was "field=$_POST(field)" with no $_POST['field'] set
  92. $exprArray[2] = null;
  93. } else {
  94. // Case of "function()" parse as if "function()==true"
  95. $exprArray[1] = '==';
  96. $exprArray[2] = true;
  97. }
  98. // if one side of the operation is a function and the other is 'true' or 'false'
  99. // then convert to Boolean true or false which signals to not try to dereference
  100. // true or false during evaluateComparison()
  101. if (is_array($exprArray[0])) {
  102. if ($exprArray[2] === 'true') {
  103. $exprArray[2] = true;
  104. } else if ($exprArray[2] === 'false') {
  105. $exprArray[2] = false;
  106. }
  107. }
  108. if (is_array($exprArray[2])) {
  109. if ($exprArray[0] === 'true') {
  110. $exprArray[0] = true;
  111. }
  112. if ($exprArray[0] === 'false') {
  113. $exprArray[0] = false;
  114. }
  115. }
  116. }
  117. $andSubTree[] = $exprArray;
  118. }
  119. $this->tree[] = $andSubTree;
  120. }
  121. }
  122. /**
  123. * Parse a comparison expression into its three components
  124. * @param $comparisonExpression string in the form 'value1' . 'operator' . 'value2' where
  125. * operator is a php comparison operator or '='
  126. * @return array of string [ value1, operator, value2 ]
  127. */
  128. public function parseExpression($comparisonExpression) {
  129. // Sometimes get HTML codes for greater-than and less-than; replace them with actual symbols
  130. $comparisonExpression = str_replace('&gt;', '>', $comparisonExpression);
  131. $comparisonExpression = str_replace('&lt;', '<', $comparisonExpression);
  132. return preg_split('/(===)|(==)|(=)|(!==)|(!=)|(<>)|(<=)|(<)|(>=)|(>)|(~~)/',
  133. $comparisonExpression, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
  134. }
  135. /**
  136. * Evaluate expression against input data. Assumes parse was called to set up the expression to
  137. * evaluate. Expression should have key . operator . value tuples and input $data should have the same keys
  138. * with values to check against them.
  139. * For example, an expression in this object is 'name=john' and the input data has [ 'name' => 'john' ]. In
  140. * this case true is returned. if $data has [ 'name' => 'fred' ] then false is returned.
  141. * @param $data array [ key => value]
  142. * @return boolean result of evaluating $data against expression tree
  143. */
  144. public function evaluate(&$data) {
  145. $this->setTimezone();
  146. $retVal = true;
  147. if ($this->tree) {
  148. $retVal = false;
  149. foreach ($this->tree as $andArray) { // loop each OR'ed $andArray
  150. $andBoolean = true;
  151. // evaluation the list of AND'ed comparison expressions
  152. foreach ($andArray as $comparison) {
  153. $andBoolean = $this->evaluateComparison($comparison, $data); //&& $andBoolean
  154. if (!$andBoolean) {
  155. break; // short-circuit AND expression evaluation
  156. }
  157. }
  158. $retVal = $retVal || $andBoolean;
  159. if ($retVal) {
  160. break; // short-circuit OR expression evaluation
  161. }
  162. }
  163. }
  164. return $retVal;
  165. }
  166. public function evaluateComparison($andExpr, &$data) {
  167. if (is_array($andExpr) && count($andExpr) == 3) {
  168. // $andExpr = [$left $op $right]
  169. // Left operand
  170. $left = $andExpr[0];
  171. // Boolean type means it was set in parse in response
  172. // to a filter like "function(x)" that was turned into an expression
  173. // like "function(x) === true"
  174. if ($left !== true && $left !== false) {
  175. if (is_array($left)) { // function call
  176. $left = $this->functionEvaluator->evaluateFunction($left, $data);
  177. } else {
  178. $left = $this->functionEvaluator->preprocessValues($left);
  179. // Dereference $left assuming it is the name of a form field
  180. // and set it to the value of the field. When not found make it null
  181. $left = isset($data[$left]) ? $data[$left] : null;
  182. }
  183. }
  184. // Operator
  185. $op = $andExpr[1];
  186. // Right operand
  187. $right = $andExpr[2];
  188. if (is_array($right)) { // function call
  189. $right = $this->functionEvaluator->evaluateFunction($right, $data);
  190. } else {
  191. $right = $this->functionEvaluator->preprocessValues($right);
  192. }
  193. if ($andExpr[0] === 'submit_time') {
  194. if (!is_numeric($right)) {
  195. $right = strtotime($right);
  196. }
  197. }
  198. if ($left === null && $right === null) {
  199. // Addresses case where 'Submitted Login' = $user_login but there exist some submissions
  200. // with no 'Submitted Login' field. Without this clause, those rows where 'Submitted Login' == null
  201. // would be returned when what we really want to is affirm that there is a 'Submitted Login' value ($left)
  202. // But we want to preserve the correct behavior for the case where 'field'=null is the constraint.
  203. return false;
  204. }
  205. return $this->evaluateLeftOpRightComparison($left, $op, $right);
  206. }
  207. return false;
  208. }
  209. /**
  210. * @param $left mixed
  211. * @param $operator string representing any PHP comparison operator or '=' which is taken to mean '=='
  212. * @param $right $mixed. SPECIAL CASE: if it is the string 'null' it is taken to be the value null
  213. * @return bool evaluation of comparison $left $operator $right
  214. */
  215. public function evaluateLeftOpRightComparison($left, $operator, $right) {
  216. if ($right === 'null') {
  217. // special case
  218. $right = null;
  219. }
  220. // Try to do numeric comparisons when possible
  221. if (is_numeric($left) && is_numeric($right)) {
  222. $left = (float)$left;
  223. $right = (float)$right;
  224. }
  225. // Could do this easier with eval() but since this text ultimately
  226. // comes form a shortcode's user-entered attributes, I want to avoid a security hole
  227. $retVal = false;
  228. switch ($operator) {
  229. case '=' :
  230. case '==':
  231. $retVal = $left == $right;
  232. break;
  233. case '===':
  234. $retVal = $left === $right;
  235. break;
  236. case '!=':
  237. $retVal = $left != $right;
  238. break;
  239. case '!==':
  240. $retVal = $left !== $right;
  241. break;
  242. case '<>':
  243. $retVal = $left <> $right;
  244. break;
  245. case '>':
  246. $retVal = $left > $right;
  247. break;
  248. case '>=':
  249. $retVal = $left >= $right;
  250. break;
  251. case '<':
  252. $retVal = $left < $right;
  253. break;
  254. case '<=':
  255. $retVal = $left <= $right;
  256. break;
  257. case '~~':
  258. $retVal = @preg_match($right, $left) > 0;
  259. break;
  260. default:
  261. trigger_error("Invalid operator: '$operator'", E_USER_NOTICE);
  262. break;
  263. }
  264. return $retVal;
  265. }
  266. }