VTJsonCondition::checkCondition()   F
last analyzed

Complexity

Conditions 159
Paths > 20000

Size

Total Lines 396
Code Lines 311

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 14077.7456

Importance

Changes 0
Metric Value
eloc 311
dl 0
loc 396
ccs 46
cts 255
cp 0.1804
rs 0
c 0
b 0
f 0
cc 159
nc 28771
nop 3
crap 14077.7456

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* +**********************************************************************************
3
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
4
 * ("License"); You may not use this file except in compliance with the License
5
 * The Original Code is:  vtiger CRM Open Source
6
 * The Initial Developer of the Original Code is vtiger.
7
 * Portions created by vtiger are Copyright (C) vtiger.
8
 * All Rights Reserved.
9
 * Contributor(s): YetiForce S.A.
10
 * ********************************************************************************** */
11
12
class VTJsonCondition
13
{
14
	/**
15
	 * Evaluate.
16
	 *
17
	 * @param array               $condition
18
	 * @param Vtiger_Record_Model $recordModel
19
	 *
20
	 * @return string
21
	 */
22 17
	public function evaluate($condition, Vtiger_Record_Model $recordModel)
23
	{
24 17
		$expr = \App\Json::decode($condition);
0 ignored issues
show
Bug introduced by
$condition of type array is incompatible with the type string expected by parameter $encodedValue of App\Json::decode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

24
		$expr = \App\Json::decode(/** @scrutinizer ignore-type */ $condition);
Loading history...
25 17
		$finalResult = true;
26 17
		if (\is_array($expr)) {
27 17
			$groupResults = [];
28 17
			$expressionResults = [];
29 17
			$i = 0;
30 17
			foreach ($expr as $cond) {
31 11
				$conditionGroup = $cond['groupid'];
32 11
				if (empty($conditionGroup)) {
33 11
					$conditionGroup = 0;
34
				}
35 11
				preg_match('/(\w+) : \((\w+)\) (\w+)/', $cond['fieldname'], $matches);
36 11
				if (0 == \count($matches)) {
37 10
					$expressionResults[$conditionGroup][$i]['result'] = $this->checkCondition($recordModel, $cond);
38
				} else {
39 1
					$referenceField = $matches[1];
40 1
					$referenceModule = $matches[2];
41 1
					$fieldname = $matches[3];
42 1
					$result = false;
43
					$referenceFieldId = $recordModel->get($referenceField);
44 1
					if (!empty($referenceFieldId)) {
45 1
						$cond['fieldname'] = $fieldname;
46 1
						if ('Users' !== $referenceModule) {
47 1
							if (\App\Record::isExists($referenceFieldId)) {
48 1
								$referenceRecordModel = Vtiger_Record_Model::getInstanceById($referenceFieldId);
49 1
								$result = $this->checkCondition($referenceRecordModel, $cond, $recordModel);
50
							}
51
						} elseif ('Users' === \App\Fields\Owner::getType($referenceFieldId) && \App\User::getUserModel($referenceFieldId)->isActive()) {
52
							$referenceRecordModel = Vtiger_Record_Model::getInstanceById($referenceFieldId, $referenceModule);
53
							$result = $this->checkCondition($referenceRecordModel, $cond, $recordModel);
54
						}
55 1
					}
56
					$expressionResults[$conditionGroup][$i]['result'] = $result;
57 11
				}
58 11
				$expressionResults[$conditionGroup][$i + 1]['logicaloperator'] = (!empty($cond['joincondition'])) ? $cond['joincondition'] : 'and';
59 11
				$groupResults[$conditionGroup]['logicaloperator'] = (!empty($cond['groupjoin'])) ? $cond['groupjoin'] : 'and';
60
				++$i;
61 17
			}
62 11
			foreach ($expressionResults as $groupId => &$groupExprResultSet) {
63 11
				$groupResult = true;
64 11
				foreach ($groupExprResultSet as &$exprResult) {
65 11
					$result = null;
66 11
					if (isset($exprResult['result'])) {
67
						$result = $exprResult['result'];
68 11
					}
69 11
					if (isset($exprResult['logicaloperator'])) {
70
						$logicalOperator = $exprResult['logicaloperator'];
71 11
					}
72 11
					if (isset($result)) { // Condition to skip last condition
73 8
						if (isset($logicalOperator)) {
74 8
							switch ($logicalOperator) {
75 8
								case 'and':
76 8
									$groupResult = ($groupResult && $result);
77
									break;
78
								case 'or':
79
									$groupResult = ($groupResult || $result);
0 ignored issues
show
introduced by
The condition $groupResult is always true.
Loading history...
80
									break;
81
								default:
82
									break;
83
							}
84 11
						} else { // Case for the first condition
85
							$groupResult = $result;
86
						}
87
					}
88 11
				}
89
				$groupResults[$groupId]['result'] = $groupResult;
90 17
			}
91 11
			foreach ($groupResults as $groupId => &$groupResult) {
92 11
				$result = $groupResult['result'];
93 11
				$logicalOperator = $groupResult['logicaloperator'];
94 11
				if (isset($result)) { // Condition to skip last condition
95 11
					if (!empty($logicalOperator)) {
96 11
						switch ($logicalOperator) {
97 11
							case 'and':
98 11
								$finalResult = ($finalResult && $result);
99
								break;
100
							case 'or':
101
								$finalResult = ($finalResult || $result);
0 ignored issues
show
introduced by
The condition $finalResult is always true.
Loading history...
102
								break;
103
							default:
104
								break;
105
						}
106
					} else { // Case for the first condition
107
						$finalResult = $result;
108
					}
109
				}
110
			}
111 17
		}
112
		return $finalResult;
113
	}
114
115
	public function startsWith($str, $subStr)
116
	{
117
		$sl = \strlen($str);
118
		$ssl = \strlen($subStr);
119
		if ($sl >= $ssl) {
120
			return 0 == substr_compare($str, $subStr, 0, $ssl);
121
		}
122
		return false;
123
	}
124
125
	public function endsWith($str, $subStr)
126
	{
127
		$sl = \strlen($str);
128
		$ssl = \strlen($subStr);
129
		if ($sl >= $ssl) {
130
			return 0 == substr_compare($str, $subStr, $sl - $ssl, $ssl);
131
		}
132
		return false;
133
	}
134
135
	/**
136
	 * Check condition.
137
	 *
138
	 * @param Vtiger_Record_Model      $recordModel
139
	 * @param array                    $cond
140
	 * @param Vtiger_Record_Model|null $referredRecordModel
141
	 *
142
	 * @throws Exception
143
	 *
144
	 * @return bool
145 11
	 */
146
	public function checkCondition(Vtiger_Record_Model $recordModel, $cond, Vtiger_Record_Model $referredRecordModel = null)
147 11
	{
148 11
		$condition = $cond['operation'];
149 11
		$fieldInstance = $recordModel->getModule()->getFieldByName($cond['fieldname']);
150 1
		if (empty($condition) || false === $fieldInstance) {
151
			return false;
152 11
		}
153 11
		$dataType = $fieldInstance->getFieldDataType();
154
		if ('datetime' === $dataType || 'date' === $dataType) {
155
			$fieldName = $cond['fieldname'];
156
			$dateTimePair = ['date_start' => 'time_start', 'due_date' => 'time_end'];
157
			if (isset($dateTimePair[$fieldName]) && !$recordModel->isEmpty($dateTimePair[$fieldName])) {
158
				$fieldValue = $recordModel->get($fieldName) . ' ' . $recordModel->get($dateTimePair[$fieldName]);
159
			} else {
160
				$fieldValue = $recordModel->get($fieldName);
161
			}
162
			$rawFieldValue = $fieldValue;
163 11
		} else {
164
			$fieldValue = $recordModel->get($cond['fieldname']);
165 11
		}
166 11
		$value = trim(html_entity_decode($cond['value'] ?? ''));
167 11
		$expressionType = $cond['valuetype'];
168
		if ('fieldname' === $expressionType) {
169
			if (null !== $referredRecordModel) {
170
				$value = $referredRecordModel->get($value);
171
			} else {
172
				$value = $recordModel->get($value);
173 11
			}
174
		} elseif ('expression' === $expressionType) {
175
			require_once 'modules/com_vtiger_workflow/expression_engine/include.php';
176
			$parser = new VTExpressionParser(new VTExpressionSpaceFilter(new VTExpressionTokenizer($value)));
177
			$expression = $parser->expression();
178
			$exprEvaluater = new VTFieldExpressionEvaluater($expression);
179
			if (null !== $referredRecordModel) {
180
				$value = $exprEvaluater->evaluate($referredRecordModel);
181
			} else {
182
				$value = $exprEvaluater->evaluate($recordModel);
183
			}
184 11
		}
185 11
		switch ($dataType) {
186
				case 'accountName':
187
					$fieldValue = $recordModel->get($fieldInstance->getName());
188 11
					$recordData = explode('|##|', $fieldValue);
189
					if (\count($recordData) > 1) {
190
						$fieldValue = trim("$recordData[0] $recordData[1]");
191
					}
192
					return $value === $fieldValue;
193
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
194 11
				case 'datetime':
195
					$fieldValue = $recordModel->get($fieldInstance->getName());
196
					break;
197 11
				case 'date':
198
					if ('between' !== $condition && strtotime($value)) {
199
						//strtotime condition is added for days before, days after where we give integer values, so strtotime will return 0 for such cases.
200 11
						$value = \App\Fields\Date::formatToDb($value, true);
201
					}
202
					break;
203
				case 'time':
204
					$value = $value . ':00'; // time fields will not have seconds appended to it, so we are adding
205
					break;
206
				case 'multiReferenceValue':
207
					$value = Vtiger_MultiReferenceValue_UIType::COMMA . $value . Vtiger_MultiReferenceValue_UIType::COMMA;
208
					break;
209
				case 'categoryMultipicklist':
210
					if (\in_array($condition, ['contains', 'contains hierarchy', 'does not contain', 'does not contain hierarchy', 'is', 'is not'])) {
211
						$value = array_filter(explode(',', $value));
212
						$fieldValue = array_filter(explode(',', $fieldValue));
213
						sort($value);
214
						sort($fieldValue);
215
						switch ($condition) {
216
							case 'is':
217
								return $value === $fieldValue;
218
							case 'is not':
219
								return $value !== $fieldValue;
220
							case 'contains':
221 11
								return empty(array_diff($value, $fieldValue));
222
							case 'contains hierarchy':
223
								$result = true;
224
								$value = \Settings_TreesManager_Record_Model::getChildren(implode('##', $value), $fieldInstance->getColumnName(), \Vtiger_Module_Model::getInstance($recordModel->getModule()->getName()));
225
								if (!empty($value)) {
226
									$value = explode('##', $value);
227
									sort($value);
228
								}
229 11
								foreach ($fieldValue as $val) {
230
									if (!\in_array($val, $value)) {
231
										$result = false;
232
									}
233
								}
234
								return $result;
235
							case 'does not contain':
236
								return !empty(array_diff($value, $fieldValue));
237
							case 'does not contain hierarchy':
238
								$result = true;
239
								$value = \Settings_TreesManager_Record_Model::getChildren(implode('##', $value), $fieldInstance->getColumnName(), \Vtiger_Module_Model::getInstance($recordModel->getModule()->getName()));
240
								if (!empty($value)) {
241
									sort($value);
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type array expected by parameter $array of sort(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

241
									sort(/** @scrutinizer ignore-type */ $value);
Loading history...
242
									$value = explode('##', $value);
243 11
								}
244
								foreach ($fieldValue as $val) {
245 11
									if (\in_array($val, $value)) {
246 11
										$result = false;
247
									}
248 11
								}
249
								return $result;
250 11
							default:
251
								break;
252 11
						}
253
					}
254 11
					$value = ",{$value},";
255
					break;
256 11
				case 'sharedOwner':
257
					if ('is' === $condition || 'is not' === $condition) {
258 11
						$fieldValueTemp = $value;
259 9
						$value = explode(',', $fieldValue);
260
						$fieldValue = $fieldValueTemp;
261
						$condition = ('is' == $condition) ? 'contains' : 'does not contain';
262
					}
263
					break;
264
				case 'owner':
265
					if ('is' === $condition || 'is not' === $condition) {
266 9
						//To avoid again checking whether it is user or not
267 10
						if (false !== strpos($value, ',')) {
268
							$value = explode(',', $value);
269
						} elseif ($value) {
270
							$value = [$value];
271
						} else {
272
							$value = [];
273
						}
274
						$condition = ('is' == $condition) ? 'contains' : 'does not contain';
275
					}
276 10
					break;
277
				case 'reference':
278
					$fieldValue = $recordModel->getDisplayValue($fieldInstance->getName(), false, true);
279
					break;
280
				default:
281
					break;
282 10
			}
283
284
		switch ($condition) {
285
			case 'equal to':
286
				return $fieldValue == $value;
287
			case 'less than':
288
				return $fieldValue < $value;
289
			case 'greater than':
290
				return $fieldValue > $value;
291 10
			case 'greaterthannow':
292
				$value = date('Y-m-d');
293 10
				if ('datetime' === $dataType) {
294
					$value = date('Y-m-d H:i:s');
295 10
				}
296
				return $fieldValue > $value;
297 10
			case 'smallerthannow':
298 8
				$value = date('Y-m-d');
299 8
				if ('datetime' === $dataType) {
300 8
					$value = date('Y-m-d H:i:s');
301
				}
302
				return $fieldValue < $value;
303 2
			case 'does not equal':
304 2
				return $fieldValue != $value;
305 2
			case 'less than or equal to':
306
				return $fieldValue <= $value;
307
			case 'greater than or equal to':
308
				return $fieldValue >= $value;
309 2
			case 'is':
310 2
				if (preg_match('/([^:]+):boolean$/', $value, $match)) {
311 2
					$value = $match[1];
312
					if ('true' == $value) {
313
						return 'on' === $fieldValue || 1 === $fieldValue || '1' === $fieldValue;
314
					}
315
					return 'off' === $fieldValue || 0 === $fieldValue || '0' === $fieldValue || '' === $fieldValue;
316
				}
317
					return $fieldValue == $value;
318
			case 'is not':
319
				if (preg_match('/([^:]+):boolean$/', $value, $match)) {
320
					$value = $match[1];
321
					if ('true' == $value) {
322
						return 'off' === $fieldValue || 0 === $fieldValue || '0' === $fieldValue || '' === $fieldValue;
323
					}
324
					return 'on' === $fieldValue || 1 === $fieldValue || '1' === $fieldValue;
325
				}
326
					return $fieldValue != $value;
327
			case 'contains':
328
				if (\is_array($value)) {
329
					return \in_array($fieldValue, $value);
330
				}
331
				return false !== strpos($fieldValue, $value);
332
			case 'does not contain':
333
				if (empty($value)) {
334
					unset($value);
335
				}
336
				if (\is_array($value)) {
337
					return !\in_array($fieldValue, $value);
338
				}
339
				return false === strpos($fieldValue, $value);
340
			case 'starts with':
341
				return $this->startsWith($fieldValue, $value);
342
			case 'ends with':
343
				return $this->endsWith($fieldValue, $value);
344
			case 'matches':
345
				return preg_match($value, $fieldValue);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match($value, $fieldValue) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
346
			case 'has changed':
347
				$hasChanged = $recordModel->getPreviousValue($cond['fieldname']);
348
				if (false === $hasChanged) {
349
					return false;
350
				}
351
				return $fieldValue != $hasChanged;
352
			case 'not has changed':
353
				return false === $recordModel->getPreviousValue($cond['fieldname']);
354
			case 'is empty':
355
				if (empty($fieldValue)) {
356
					return true;
357
				}
358
359
				return false;
360
			case 'is not empty':
361
				if (empty($fieldValue)) {
362
					return false;
363
				}
364
365
				return true;
366
			case 'before':
367
				if (empty($fieldValue)) {
368
					return false;
369
				}
370
				if ($fieldValue < $value) {
371
					return true;
372
				}
373
374
				return false;
375
			case 'after':
376
				if (empty($fieldValue)) {
377
					return false;
378
				}
379
				if ($fieldValue > $value) {
380
					return true;
381
				}
382
383
				return false;
384
			case 'between':
385
				if (empty($fieldValue)) {
386
					return false;
387
				}
388
				$values = explode(',', $value);
389
				$values = array_map('\App\Fields\Date::formatToDb', $values);
390
				if ($fieldValue > $values[0] && $fieldValue < $values[1]) {
391
					return true;
392
				}
393
394
				return false;
395
			case 'is today':
396
				if ($fieldValue == date('Y-m-d')) {
397
					return true;
398
				}
399
400
				return false;
401
			case 'less than days ago':
402
				if (empty($fieldValue) || empty($value)) {
403
					return false;
404
				}
405
				$olderDate = date('Y-m-d', strtotime('-' . $value . ' days'));
406
				if ($olderDate <= $fieldValue && $fieldValue <= date('Y-m-d')) {
407
					return true;
408
				}
409
410
				return false;
411
			case 'more than days ago':
412
				if (empty($fieldValue) || empty($value)) {
413
					return false;
414
				}
415
				$olderDate = date('Y-m-d', strtotime('-' . $value . ' days'));
416
				if ($fieldValue <= $olderDate) {
417
					return true;
418
				}
419
420
				return false;
421
			case 'in less than':
422
				if (empty($fieldValue) || empty($value)) {
423
					return false;
424
				}
425
				$today = date('Y-m-d');
426
				$futureDate = date('Y-m-d', strtotime('+' . $value . ' days'));
427
				if ($today <= $fieldValue && $fieldValue <= $futureDate) {
428
					return true;
429
				}
430
431
				return false;
432
			case 'in more than':
433
				if (empty($fieldValue) || empty($value)) {
434
					return false;
435
				}
436
				$futureDate = date('Y-m-d', strtotime('+' . $value . ' days'));
437
				if ($fieldValue >= $futureDate) {
438
					return true;
439
				}
440
441
				return false;
442
			case 'days ago':
443
				if (empty($fieldValue) || empty($value)) {
444
					return false;
445
				}
446
				$olderDate = date('Y-m-d', strtotime('-' . $value . ' days'));
447
				if ($fieldValue == $olderDate) {
448
					return true;
449
				}
450
451
				return false;
452
			case 'days later':
453
				if (empty($fieldValue) || empty($value)) {
454
					return false;
455
				}
456
				$futureDate = date('Y-m-d', strtotime('+' . $value . ' days'));
457
				if ($fieldValue == $futureDate) {
458
					return true;
459
				}
460
461
				return false;
462
			case 'less than hours before':
463
				if (empty($rawFieldValue) || empty($value)) {
464
					return false;
465
				}
466
				$currentTime = date('Y-m-d H:i:s');
467
				$olderDateTime = date('Y-m-d H:i:s', strtotime('-' . $value . ' hours'));
468
				if ($olderDateTime <= $rawFieldValue && $rawFieldValue <= $currentTime) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rawFieldValue does not seem to be defined for all execution paths leading up to this point.
Loading history...
469
					return true;
470
				}
471
472
				return false;
473
			case 'less than hours later':
474
				if (empty($rawFieldValue) || empty($value)) {
475
					return false;
476
				}
477
				$currentTime = date('Y-m-d H:i:s');
478
				$futureDateTime = date('Y-m-d H:i:s', strtotime('+' . $value . ' hours'));
479
				if ($currentTime <= $rawFieldValue && $rawFieldValue <= $futureDateTime) {
480
					return true;
481
				}
482
483
				return false;
484
			case 'more than hours before':
485
				if (empty($rawFieldValue) || empty($value)) {
486
					return false;
487
				}
488
				$olderDateTime = date('Y-m-d H:i:s', strtotime('-' . $value . ' hours'));
489
				if ($rawFieldValue <= $olderDateTime) {
490
					return true;
491
				}
492
				return false;
493
			case 'more than hours later':
494
				if (empty($rawFieldValue) || empty($value)) {
495
					return false;
496
				}
497
				$futureDateTime = date('Y-m-d H:i:s', strtotime('+' . $value . ' hours'));
498
				if ($rawFieldValue >= $futureDateTime) {
499
					return true;
500
				}
501
				return false;
502
			case 'has changed to':
503
				$oldValue = $recordModel->getPreviousValue($cond['fieldname']);
504
				return ($recordModel->isNew() || false !== $oldValue) && $recordModel->get($cond['fieldname']) == $value;
505
			case 'is Watching Record':
506
				$watchdog = Vtiger_Watchdog_Model::getInstanceById($recordModel->getId(), $recordModel->getModuleName());
507
				if ($watchdog->isWatchingRecord()) {
508
					return true;
509
				}
510
				return false;
511
			case 'is Not Watching Record':
512
				$watchdog = Vtiger_Watchdog_Model::getInstanceById($recordModel->getId(), $recordModel->getModuleName());
513
				if ($watchdog->isWatchingRecord()) {
514
					return false;
515
				}
516
				return true;
517
			case 'om':
518
				return (int) $value !== \App\User::getCurrentUserId() ? false : true;
519
			case 'nom':
520
				return (int) $value === \App\User::getCurrentUserId() ? false : true;
521
			case 'is record open':
522
				if (
523
					($fieldName = App\RecordStatus::getFieldName($recordModel->getModule()->getName()))
0 ignored issues
show
Bug introduced by
$recordModel->getModule()->getName() of type boolean is incompatible with the type string expected by parameter $moduleName of App\RecordStatus::getFieldName(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

523
					($fieldName = App\RecordStatus::getFieldName(/** @scrutinizer ignore-type */ $recordModel->getModule()->getName()))
Loading history...
524
				&& \in_array($recordModel->get($fieldName), App\RecordStatus::getStates($recordModel->getModule()->getName(), \App\RecordStatus::RECORD_STATE_OPEN))
0 ignored issues
show
Bug introduced by
$recordModel->getModule()->getName() of type boolean is incompatible with the type string expected by parameter $moduleName of App\RecordStatus::getStates(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

524
				&& \in_array($recordModel->get($fieldName), App\RecordStatus::getStates(/** @scrutinizer ignore-type */ $recordModel->getModule()->getName(), \App\RecordStatus::RECORD_STATE_OPEN))
Loading history...
525
				) {
526
					return true;
527
				}
528
				return false;
529
			case 'is record closed':
530
				if (
531
					($fieldName = App\RecordStatus::getFieldName($recordModel->getModule()->getName()))
532
				&& \in_array($recordModel->get($fieldName), App\RecordStatus::getStates($recordModel->getModule()->getName(), \App\RecordStatus::RECORD_STATE_CLOSED))
533
				) {
534
					return false;
535
				}
536
				return true;
537
			case 'not created by owner':
538
				return $recordModel->get($fieldInstance->getName()) !== $recordModel->get('created_user_id');
539
			default:
540
				//Unexpected condition
541
				throw new \App\Exceptions\AppException('Found an unexpected condition: ' . $condition);
542
		}
543
	}
544
}
545