PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / 3rdparty / SleekDB / Classes / ConditionsHandler.php
backup / src / JetBackup / 3rdparty / SleekDB / Classes Last commit date
.htaccess 1 year ago CacheHandler.php 1 year ago ConditionsHandler.php 1 year ago DocumentFinder.php 1 year ago DocumentReducer.php 1 year ago DocumentUpdater.php 4 months ago IoHelper.php 4 months ago NestedHelper.php 1 year ago index.html 1 year ago web.config 1 year ago
ConditionsHandler.php
458 lines
1 <?php
2
3
4 namespace SleekDB\Classes;
5
6
7 use SleekDB\Exceptions\InvalidArgumentException;
8 use DateTime;
9 use Exception;
10 use Throwable;
11
12 /**
13 * Class ConditionsHandler
14 * Handle all types of conditions to check if a document has passed.
15 */
16 class ConditionsHandler
17 {
18
19 /**
20 * Get the result of an condition.
21 * @param string $condition
22 * @param mixed $fieldValue value of current field
23 * @param mixed $value value to check
24 * @return bool
25 * @throws InvalidArgumentException
26 */
27 public static function verifyCondition(string $condition, $fieldValue, $value): bool
28 {
29
30 if($value instanceof DateTime){
31 // compare timestamps
32
33 // null, false or an empty string will convert to current date and time.
34 // That is not what we want.
35 if(empty($fieldValue)){
36 return false;
37 }
38 $value = $value->getTimestamp();
39 $fieldValue = self::convertValueToTimeStamp($fieldValue);
40 }
41
42 $condition = strtolower(trim($condition));
43 switch ($condition){
44 case "=":
45 case "===":
46 return ($fieldValue === $value);
47 case "==":
48 return ($fieldValue == $value);
49 case "<>":
50 return ($fieldValue != $value);
51 case "!==":
52 case "!=":
53 return ($fieldValue !== $value);
54 case ">":
55 return ($fieldValue > $value);
56 case ">=":
57 return ($fieldValue >= $value);
58 case "<":
59 return ($fieldValue < $value);
60 case "<=":
61 return ($fieldValue <= $value);
62 case "not like":
63 case "like":
64
65 if(!is_string($value)){
66 throw new InvalidArgumentException("When using \"LIKE\" or \"NOT LIKE\" the value has to be a string.");
67 }
68
69 // escape characters that are part of regular expression syntax
70 // https://www.php.net/manual/en/function.preg-quote.php
71 // We can not use preg_quote because the following characters are also wildcard characters in sql
72 // so we will not escape them: [ ^ ] -
73 $charactersToEscape = array(
74 '\\' => '\\\\', // Escape backslash to prevent unwanted behaviour
75 '/' => '\/', // slash needs to be escaped because it's used as delimiter
76 '.' => '\.',
77 '+' => '\+',
78 '*' => '\*',
79 '?' => '\?',
80 '$' => '\$',
81 '(' => '\(',
82 ')' => '\)',
83 '{' => '\{',
84 '}' => '\}',
85 '=' => '\=',
86 '!' => '\!',
87 '<' => '\<',
88 '>' => '\>',
89 '|' => '\|',
90 ':' => '\:',
91 '#' => '\#',
92 '%' => '.*',
93 '_' => '.{1}',
94 // Allow escaping of % and _ with backslash
95 '\.*' => '%',
96 '\.{1}' => '_',
97 // Allow escaping of wildcards
98 '\\\\[' => '\[',
99 '\\\\^' => '\^',
100 '\\\\]' => '\]',
101 '\\\\-' => '\-',
102 );
103 $value = str_replace(array_keys($charactersToEscape), array_values($charactersToEscape), $value); // (zero or more characters) and (single character)
104 $pattern = '/^' . $value . '$/i';
105 $result = is_string($fieldValue) && (preg_match($pattern, $fieldValue) === 1);
106 return ($condition === 'not like') ? !$result : $result;
107
108 case "not in":
109 case "in":
110 if(!is_array($value)){
111 $value = (!is_object($value) && !is_array($value) && !is_null($value)) ? $value : gettype($value);
112 throw new InvalidArgumentException("When using \"in\" and \"not in\" you have to check against an array. Got: $value");
113 }
114 if(!empty($value)){
115 (list($firstElement) = $value);
116 if($firstElement instanceof DateTime){
117 // if the user wants to use DateTime, every element of the array has to be an DateTime object.
118
119 // compare timestamps
120
121 // null, false or an empty string will convert to current date and time.
122 // That is not what we want.
123 if(empty($fieldValue)){
124 return false;
125 }
126
127 foreach ($value as $key => $item){
128 if(!($item instanceof DateTime)){
129 throw new InvalidArgumentException("If one DateTime object is given in an \"IN\" or \"NOT IN\" comparison, every element has to be a DateTime object!");
130 }
131 $value[$key] = $item->getTimestamp();
132 }
133
134 $fieldValue = self::convertValueToTimeStamp($fieldValue);
135 }
136 }
137 $result = in_array($fieldValue, $value, true);
138 return ($condition === "not in") ? !$result : $result;
139 case "not between":
140 case "between":
141
142 if(!is_array($value) || ($valueLength = count($value)) !== 2){
143 $value = (!is_object($value) && !is_array($value) && !is_null($value)) ? $value : gettype($value);
144 if(isset($valueLength)){
145 $value .= " | Length: $valueLength";
146 }
147 throw new InvalidArgumentException("When using \"between\" you have to check against an array with a length of 2. Got: $value");
148 }
149
150 list($startValue, $endValue) = $value;
151
152 $result = (
153 self::verifyCondition(">=", $fieldValue, $startValue)
154 && self::verifyCondition("<=", $fieldValue, $endValue)
155 );
156
157 return ($condition === "not between") ? !$result : $result;
158 case "not contains":
159 case "contains":
160
161 if(!is_array($fieldValue)){
162 return ($condition === "not contains");
163 }
164
165 $fieldValues = [];
166
167 if($value instanceof DateTime){
168 // compare timestamps
169 $value = $value->getTimestamp();
170
171 foreach ($fieldValue as $item){
172 // null, false or an empty string will convert to current date and time.
173 // That is not what we want.
174 if(empty($item)){
175 continue;
176 }
177 try{
178 $fieldValues[] = self::convertValueToTimeStamp($item);
179 } catch (Exception $exception){
180 }
181 }
182 }
183
184 if(!empty($fieldValues)){
185 $result = in_array($value, $fieldValues, true);
186 } else {
187 $result = in_array($value, $fieldValue, true);
188 }
189
190 return ($condition === "not contains") ? !$result : $result;
191 case 'exists':
192 return $fieldValue === $value;
193 default:
194 throw new InvalidArgumentException("Condition \"$condition\" is not allowed.");
195 }
196 }
197
198 /**
199 * @param array $element condition or operation
200 * @param array $data
201 * @return bool
202 * @throws InvalidArgumentException
203 */
204 public static function handleWhereConditions(array $element, array $data): bool
205 {
206 if(empty($element)){
207 throw new InvalidArgumentException("Malformed where statement! Where statements can not contain empty arrays.");
208 }
209 if(array_keys($element) !== range(0, (count($element) - 1))){
210 throw new InvalidArgumentException("Malformed where statement! Associative arrays are not allowed.");
211 }
212 // element is a where condition
213 if(is_string($element[0]) && is_string($element[1])){
214 if(count($element) !== 3){
215 throw new InvalidArgumentException("Where conditions have to be [fieldName, condition, value]");
216 }
217
218 $fieldName = $element[0];
219 $condition = strtolower(trim($element[1]));
220 $fieldValue = ($condition === 'exists')
221 ? NestedHelper::nestedFieldExists($fieldName, $data)
222 : NestedHelper::getNestedValue($fieldName, $data);
223
224 return self::verifyCondition($condition, $fieldValue, $element[2]);
225 }
226
227 // element is an array "brackets"
228
229 // prepare results array - example: [true, "and", false]
230 $results = [];
231 foreach ($element as $value){
232 if(is_array($value)){
233 $results[] = self::handleWhereConditions($value, $data);
234 } else if (is_string($value)) {
235 $results[] = $value;
236 } else if($value instanceof \Closure){
237 $result = $value($data);
238 if(!is_bool($result)){
239 $resultType = gettype($result);
240 $errorMsg = "The closure in the where condition needs to return a boolean. Got: $resultType";
241 throw new InvalidArgumentException($errorMsg);
242 }
243 $results[] = $result;
244 } else {
245 $value = (!is_object($value) && !is_array($value) && !is_null($value)) ? $value : gettype($value);
246 throw new InvalidArgumentException("Invalid nested where statement element! Expected condition or operation, got: \"$value\"");
247 }
248 }
249
250 // first result as default value
251 $returnValue = array_shift($results);
252
253 if(is_bool($returnValue) === false){
254 throw new InvalidArgumentException("Malformed where statement! First part of the statement have to be a condition.");
255 }
256
257 // used to prioritize the "and" operation.
258 $orResults = [];
259
260 // use results array to get the return value of the conditions within the bracket
261 while(!empty($results) || !empty($orResults)){
262
263 if(empty($results)) {
264 if($returnValue === true){
265 // we need to check anymore, because the result of true || false is true
266 break;
267 }
268 // $orResults is not empty.
269 $nextResult = array_shift($orResults);
270 $returnValue = $returnValue || $nextResult;
271 continue;
272 }
273
274 $operationOrNextResult = array_shift($results);
275
276 if(is_string($operationOrNextResult)){
277 $operation = $operationOrNextResult;
278
279 if(empty($results)){
280 throw new InvalidArgumentException("Malformed where statement! Last part of a condition can not be a operation.");
281 }
282 $nextResult = array_shift($results);
283
284 if(!is_bool($nextResult)){
285 throw new InvalidArgumentException("Malformed where statement! Two operations in a row are not allowed.");
286 }
287 } else if(is_bool($operationOrNextResult)){
288 $operation = "AND";
289 $nextResult = $operationOrNextResult;
290 } else {
291 throw new InvalidArgumentException("Malformed where statement! A where statement have to contain just operations and conditions.");
292 }
293
294 if(!in_array(strtolower($operation), ["and", "or"])){
295 $operation = (!is_object($operation) && !is_array($operation) && !is_null($operation)) ? $operation : gettype($operation);
296 throw new InvalidArgumentException("Expected 'and' or 'or' operator got \"$operation\"");
297 }
298
299 // prepare $orResults execute after all "and" are done.
300 if(strtolower($operation) === "or"){
301 $orResults[] = $returnValue;
302 $returnValue = $nextResult;
303 continue;
304 }
305
306 $returnValue = $returnValue && $nextResult;
307
308 }
309
310 return $returnValue;
311 }
312
313 /**
314 * @param array $results
315 * @param array $currentDocument
316 * @param array $distinctFields
317 * @return bool
318 */
319 public static function handleDistinct(array $results, array $currentDocument, array $distinctFields): bool
320 {
321 // Distinct data check.
322 foreach ($results as $result) {
323 foreach ($distinctFields as $field) {
324 try {
325 $storePassed = (NestedHelper::getNestedValue($field, $result) !== NestedHelper::getNestedValue($field, $currentDocument));
326 } catch (Throwable $th) {
327 continue;
328 }
329 if ($storePassed === false) {
330 return false;
331 }
332 }
333 }
334
335 return true;
336 }
337
338 /**
339 * @param array $data
340 * @param bool $storePassed
341 * @param array $nestedWhereConditions
342 * @return bool
343 * @throws InvalidArgumentException
344 * @deprecated since version 2.3, use handleWhereConditions instead.
345 */
346 public static function handleNestedWhere(array $data, bool $storePassed, array $nestedWhereConditions): bool
347 {
348 // TODO remove nested where with v3.0
349
350 if(empty($nestedWhereConditions)){
351 return $storePassed;
352 }
353
354 // the outermost operation specify how the given conditions are connected with other conditions,
355 // like the ones that are specified using the where, orWhere, in or notIn methods
356 $outerMostOperation = (array_keys($nestedWhereConditions))[0];
357 $nestedConditions = $nestedWhereConditions[$outerMostOperation];
358
359 // specifying outermost is optional and defaults to "and"
360 $outerMostOperation = (is_string($outerMostOperation)) ? strtolower($outerMostOperation) : "and";
361
362 // if the document already passed the store with another condition, we dont need to check it.
363 if($outerMostOperation === "or" && $storePassed === true){
364 return true;
365 }
366
367 return self::_nestedWhereHelper($nestedConditions, $data);
368 }
369
370 /**
371 * @param array $element
372 * @param array $data
373 * @return bool
374 * @throws InvalidArgumentException
375 * @deprecated since version 2.3. use _handleWhere instead
376 */
377 private static function _nestedWhereHelper(array $element, array $data): bool
378 {
379 // TODO remove nested where with v3.0
380 // element is a where condition
381 if(array_keys($element) === range(0, (count($element) - 1)) && is_string($element[0])){
382 if(count($element) !== 3){
383 throw new InvalidArgumentException("Where conditions have to be [fieldName, condition, value]");
384 }
385
386 $fieldValue = NestedHelper::getNestedValue($element[0], $data);
387
388 return self::verifyCondition($element[1], $fieldValue, $element[2]);
389 }
390
391 // element is an array "brackets"
392
393 // prepare results array - example: [true, "and", false]
394 $results = [];
395 foreach ($element as $value){
396 if(is_array($value)){
397 $results[] = self::_nestedWhereHelper($value, $data);
398 } else if (is_string($value)){
399 $results[] = $value;
400 } else {
401 $value = (!is_object($value) && !is_array($value)) ? $value : gettype($value);
402 throw new InvalidArgumentException("Invalid nested where statement element! Expected condition or operation, got: \"$value\"");
403 }
404 }
405
406 if(count($results) < 3){
407 throw new InvalidArgumentException("Malformed nested where statement! A condition consists of at least 3 elements.");
408 }
409
410 // first result as default value
411 $returnValue = array_shift($results);
412
413 // use results array to get the return value of the conditions within the bracket
414 while(!empty($results)){
415 $operation = array_shift($results);
416 $nextResult = array_shift($results);
417
418 if(((count($results) % 2) !== 0)){
419 throw new InvalidArgumentException("Malformed nested where statement!");
420 }
421
422 if(!is_string($operation) || !in_array(strtolower($operation), ["and", "or"])){
423 $operation = (!is_object($operation) && !is_array($operation)) ? $operation : gettype($operation);
424 throw new InvalidArgumentException("Expected 'and' or 'or' operator got \"$operation\"");
425 }
426
427 if(strtolower($operation) === "and"){
428 $returnValue = $returnValue && $nextResult;
429 } else {
430 $returnValue = $returnValue || $nextResult;
431 }
432 }
433
434 return $returnValue;
435 }
436
437 /**
438 * @param $value
439 * @return int
440 * @throws InvalidArgumentException
441 */
442 private static function convertValueToTimeStamp($value): int
443 {
444 $value = (is_string($value)) ? trim($value) : $value;
445 try{
446 return (new DateTime($value))->getTimestamp();
447 } catch (Exception $exception){
448 $value = (!is_object($value) && !is_array($value))
449 ? $value
450 : gettype($value);
451 throw new InvalidArgumentException(
452 "DateTime object given as value to check against. "
453 . "Could not convert value into DateTime. "
454 . "Value: $value"
455 );
456 }
457 }
458 }