This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Condition main class. |
||
5 | * |
||
6 | * @package App |
||
7 | * |
||
8 | * @copyright YetiForce S.A. |
||
9 | * @license YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com) |
||
10 | * @author Tomasz Kur <[email protected]> |
||
11 | * @author Mariusz Krzaczkowski <[email protected]> |
||
12 | * @author Radosław Skrzypczak <[email protected]> |
||
13 | */ |
||
14 | |||
15 | namespace App; |
||
16 | |||
17 | /** |
||
18 | * Condition main class. |
||
19 | */ |
||
20 | class Condition |
||
21 | { |
||
22 | /** |
||
23 | * @var array Data filter list. |
||
24 | */ |
||
25 | const DATE_OPERATORS = [ |
||
26 | 'custom' => ['label' => 'LBL_CUSTOM'], |
||
27 | 'smallerthannow' => ['label' => 'LBL_SMALLER_THAN_NOW'], |
||
28 | 'greaterthannow' => ['label' => 'LBL_GREATER_THAN_NOW'], |
||
29 | 'prevfy' => ['label' => 'LBL_PREVIOUS_FY'], |
||
30 | 'thisfy' => ['label' => 'LBL_CURRENT_FY'], |
||
31 | 'nextfy' => ['label' => 'LBL_NEXT_FY'], |
||
32 | 'prevfq' => ['label' => 'LBL_PREVIOUS_FQ'], |
||
33 | 'thisfq' => ['label' => 'LBL_CURRENT_FQ'], |
||
34 | 'nextfq' => ['label' => 'LBL_NEXT_FQ'], |
||
35 | 'previousworkingday' => ['label' => 'LBL_PREVIOUS_WORKING_DAY'], |
||
36 | 'nextworkingday' => ['label' => 'LBL_NEXT_WORKING_DAY'], |
||
37 | 'yesterday' => ['label' => 'LBL_YESTERDAY'], |
||
38 | 'today' => ['label' => 'LBL_TODAY'], |
||
39 | 'untiltoday' => ['label' => 'LBL_UNTIL_TODAY'], |
||
40 | 'tomorrow' => ['label' => 'LBL_TOMORROW'], |
||
41 | 'lastweek' => ['label' => 'LBL_LAST_WEEK'], |
||
42 | 'thisweek' => ['label' => 'LBL_CURRENT_WEEK'], |
||
43 | 'nextweek' => ['label' => 'LBL_NEXT_WEEK'], |
||
44 | 'lastmonth' => ['label' => 'LBL_LAST_MONTH'], |
||
45 | 'thismonth' => ['label' => 'LBL_CURRENT_MONTH'], |
||
46 | 'nextmonth' => ['label' => 'LBL_NEXT_MONTH'], |
||
47 | 'last7days' => ['label' => 'LBL_LAST_7_DAYS'], |
||
48 | 'last15days' => ['label' => 'LBL_LAST_15_DAYS'], |
||
49 | 'last30days' => ['label' => 'LBL_LAST_30_DAYS'], |
||
50 | 'last60days' => ['label' => 'LBL_LAST_60_DAYS'], |
||
51 | 'last90days' => ['label' => 'LBL_LAST_90_DAYS'], |
||
52 | 'last120days' => ['label' => 'LBL_LAST_120_DAYS'], |
||
53 | 'next15days' => ['label' => 'LBL_NEXT_15_DAYS'], |
||
54 | 'next30days' => ['label' => 'LBL_NEXT_30_DAYS'], |
||
55 | 'next60days' => ['label' => 'LBL_NEXT_60_DAYS'], |
||
56 | 'next90days' => ['label' => 'LBL_NEXT_90_DAYS'], |
||
57 | 'next120days' => ['label' => 'LBL_NEXT_120_DAYS'], |
||
58 | 'moreThanDaysAgo' => ['label' => 'LBL_DATE_CONDITION_MORE_THAN_DAYS_AGO'], |
||
59 | ]; |
||
60 | |||
61 | /** |
||
62 | * @var string[] List of field comparison operators |
||
63 | */ |
||
64 | const FIELD_COMPARISON_OPERATORS = ['ef', 'nf', 'lf', 'gf', 'mf', 'hf']; |
||
65 | |||
66 | /** |
||
67 | * @var string[] Supported advanced filter operations. |
||
68 | */ |
||
69 | const STANDARD_OPERATORS = [ |
||
70 | 'e' => 'LBL_EQUALS', |
||
71 | 'n' => 'LBL_NOT_EQUAL_TO', |
||
72 | 's' => 'LBL_STARTS_WITH', |
||
73 | 'ew' => 'LBL_ENDS_WITH', |
||
74 | 'c' => 'LBL_CONTAINS', |
||
75 | 'ch' => 'LBL_CONTAINS_HIERARCHY', |
||
76 | 'k' => 'LBL_DOES_NOT_CONTAIN', |
||
77 | 'kh' => 'LBL_DOES_NOT_CONTAIN_HIERARCHY', |
||
78 | 'l' => 'LBL_LESS_THAN', |
||
79 | 'g' => 'LBL_GREATER_THAN', |
||
80 | 'm' => 'LBL_LESS_THAN_OR_EQUAL', |
||
81 | 'h' => 'LBL_GREATER_OR_EQUAL', |
||
82 | 'b' => 'LBL_BEFORE', |
||
83 | 'a' => 'LBL_AFTER', |
||
84 | 'bw' => 'LBL_BETWEEN', |
||
85 | 'y' => 'LBL_IS_EMPTY', |
||
86 | 'ny' => 'LBL_IS_NOT_EMPTY', |
||
87 | 'om' => 'LBL_CURRENTLY_LOGGED_USER', |
||
88 | 'nom' => 'LBL_USER_CURRENTLY_NOT_LOGGED', |
||
89 | 'ogr' => 'LBL_CURRENTLY_LOGGED_USER_GROUP', |
||
90 | 'ogu' => 'LBL_USERS_GROUP_LOGGED_IN_USER', |
||
91 | 'wr' => 'LBL_IS_WATCHING_RECORD', |
||
92 | 'nwr' => 'LBL_IS_NOT_WATCHING_RECORD', |
||
93 | 'hs' => 'LBL_HAS_CHANGED', |
||
94 | 'hst' => 'LBL_HAS_CHANGED_TO', |
||
95 | 'ro' => 'LBL_IS_RECORD_OPEN', |
||
96 | 'rc' => 'LBL_IS_RECORD_CLOSED', |
||
97 | 'nco' => 'LBL_NOT_CREATED_BY_OWNER', |
||
98 | 'ef' => 'LBL_EQUALS_FIELD', |
||
99 | 'nf' => 'LBL_NOT_EQUAL_TO_FIELD', |
||
100 | 'lf' => 'LBL_LESS_THAN_FIELD', |
||
101 | 'gf' => 'LBL_GREATER_THAN_FIELD', |
||
102 | 'mf' => 'LBL_LESS_THAN_OR_EQUAL_FIELD', |
||
103 | 'hf' => 'LBL_GREATER_OR_EQUAL_FIELD', |
||
104 | ]; |
||
105 | |||
106 | /** |
||
107 | * @var string[] Operators without values. |
||
108 | */ |
||
109 | const OPERATORS_WITHOUT_VALUES = [ |
||
110 | 'y', 'ny', 'om', 'nom', 'ogr', 'wr', 'nwr', 'hs', 'ro', 'rc', 'nco', 'ogu', |
||
111 | 'smallerthannow', |
||
112 | 'greaterthannow', |
||
113 | 'prevfy', |
||
114 | 'thisfy', |
||
115 | 'nextfy', |
||
116 | 'prevfq', |
||
117 | 'thisfq', |
||
118 | 'yesterday', |
||
119 | 'today', |
||
120 | 'untiltoday', |
||
121 | 'tomorrow', |
||
122 | 'lastweek', |
||
123 | 'thisweek', |
||
124 | 'nextweek', |
||
125 | 'lastmonth', |
||
126 | 'thismonth', |
||
127 | 'nextmonth', |
||
128 | 'last7days', |
||
129 | 'last15days', |
||
130 | 'last30days', |
||
131 | 'last60days', |
||
132 | 'last90days', |
||
133 | 'last120days', |
||
134 | 'next15days', |
||
135 | 'next30days', |
||
136 | 'next60days', |
||
137 | 'next90days', |
||
138 | 'next120days', |
||
139 | 'previousworkingday', |
||
140 | 'nextworkingday', |
||
141 | ]; |
||
142 | |||
143 | /** |
||
144 | * Vtiger_Record_Model instance cache. |
||
145 | * |
||
146 | * @var Vtiger_Record_Model[] |
||
147 | */ |
||
148 | private static $recordCache = []; |
||
149 | |||
150 | /** |
||
151 | * Checks structure search_params. |
||
152 | * |
||
153 | * @param string $moduleName |
||
154 | * @param array $searchParams |
||
155 | * @param bool $convert |
||
156 | * |
||
157 | * @throws \App\Exceptions\IllegalValue |
||
158 | * |
||
159 | * @return array |
||
160 | */ |
||
161 | public static function validSearchParams(string $moduleName, array $searchParams, $convert = true): array |
||
162 | { |
||
163 | $searchParamsCount = \count($searchParams); |
||
164 | if ($searchParamsCount > 2) { |
||
165 | throw new Exceptions\IllegalValue("ERR_NUMBER_OF_ARGUMENTS_NOT_ALLOWED||{$searchParamsCount}|| > 2||" . Utils::varExport($searchParams, true), 406); |
||
0 ignored issues
–
show
|
|||
166 | } |
||
167 | $fields = \Vtiger_Module_Model::getInstance($moduleName)->getFields(); |
||
168 | $result = []; |
||
169 | foreach ($searchParams as $params) { |
||
170 | $tempParam = []; |
||
171 | foreach ($params as $param) { |
||
172 | if (empty($param)) { |
||
173 | continue; |
||
174 | } |
||
175 | $count = \count($param); |
||
176 | if (3 !== $count && 4 !== $count) { |
||
177 | throw new Exceptions\IllegalValue("ERR_NUMBER_OF_ARGUMENTS_NOT_ALLOWED||{$count}|| <> 3 or 4||" . Utils::varExport($param, true), 406); |
||
178 | } |
||
179 | [$relatedFieldName, $relatedModule, $referenceField] = array_pad(explode(':', $param[0]), 3, null); |
||
180 | if ($relatedModule) { |
||
181 | $relatedFields = \Vtiger_Module_Model::getInstance($relatedModule)->getFields(); |
||
182 | if (!isset($fields[$referenceField], $relatedFields[$relatedFieldName])) { |
||
183 | throw new Exceptions\IllegalValue("ERR_FIELD_NOT_FOUND||{$param[0]}||" . Utils::varExport($param, true), 406); |
||
184 | } |
||
185 | $value = $relatedFields[$relatedFieldName]->getUITypeModel()->getDbConditionBuilderValue($param[2], $param[1]); |
||
186 | } elseif (0 === strpos($param[0], 'relationColumn_') && preg_match('/(^relationColumn_)(\d+)$/', $param[0])) { |
||
187 | $value = (int) $param[2]; |
||
188 | } else { |
||
189 | if (!isset($fields[$param[0]])) { |
||
190 | throw new Exceptions\IllegalValue("ERR_FIELD_NOT_FOUND||{$param[0]}||" . Utils::varExport($param, true), 406); |
||
191 | } |
||
192 | $value = $fields[$param[0]]->getUITypeModel()->getDbConditionBuilderValue($param[2], $param[1]); |
||
193 | } |
||
194 | if ($convert) { |
||
195 | $param[2] = $value; |
||
196 | } |
||
197 | $tempParam[] = $param; |
||
198 | } |
||
199 | $result[] = $tempParam; |
||
200 | } |
||
201 | return $result; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Checks value search_value. |
||
206 | * |
||
207 | * @param string $value |
||
208 | * @param string $moduleName |
||
209 | * @param string $fieldName |
||
210 | * @param string $operator |
||
211 | * |
||
212 | * @return string |
||
213 | */ |
||
214 | public static function validSearchValue(string $value, string $moduleName, string $fieldName, string $operator): string |
||
215 | { |
||
216 | if ('' !== $value) { |
||
217 | \Vtiger_Module_Model::getInstance($moduleName)->getField($fieldName)->getUITypeModel()->getDbConditionBuilderValue($value, $operator); |
||
218 | } |
||
219 | return $value; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Return condition from request. |
||
224 | * |
||
225 | * @param array $conditions |
||
226 | * |
||
227 | * @return array |
||
228 | */ |
||
229 | public static function getConditionsFromRequest(array $conditions): array |
||
230 | { |
||
231 | if (isset($conditions['rules'])) { |
||
232 | foreach ($conditions['rules'] as &$condition) { |
||
233 | if (isset($condition['condition'])) { |
||
234 | $condition = static::getConditionsFromRequest($condition); |
||
235 | } else { |
||
236 | $operator = $condition['operator']; |
||
237 | $value = $condition['value'] ?? ''; |
||
238 | if (!\in_array($operator, array_merge(self::OPERATORS_WITHOUT_VALUES, self::FIELD_COMPARISON_OPERATORS, array_keys(self::DATE_OPERATORS)))) { |
||
239 | [$fieldName, $fieldModuleName,] = array_pad(explode(':', $condition['fieldname']), 3, false); |
||
240 | $value = \Vtiger_Module_Model::getInstance($fieldModuleName)->getFieldByName($fieldName) |
||
241 | ->getUITypeModel() |
||
242 | ->getDbConditionBuilderValue($value, $operator); |
||
243 | } |
||
244 | $condition['value'] = $value; |
||
245 | } |
||
246 | } |
||
247 | } |
||
248 | return $conditions; |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * Check all conditions. |
||
253 | * |
||
254 | * @param array $conditions |
||
255 | * @param \Vtiger_Record_Model $recordModel |
||
256 | * |
||
257 | * @return bool |
||
258 | */ |
||
259 | public static function checkConditions(array $conditions, \Vtiger_Record_Model $recordModel): bool |
||
260 | { |
||
261 | return self::parseConditions($conditions, $recordModel); |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * Parse conditions. |
||
266 | * |
||
267 | * @param array|null $conditions |
||
268 | * @param \Vtiger_Record_Model $recordModel |
||
269 | * |
||
270 | * @return bool |
||
271 | */ |
||
272 | private static function parseConditions(?array $conditions, \Vtiger_Record_Model $recordModel): bool |
||
273 | { |
||
274 | if (empty($conditions) || empty($conditions['rules'])) { |
||
275 | return true; |
||
276 | } |
||
277 | $result = false; |
||
278 | $andCondition = 'AND' === $conditions['condition']; |
||
279 | foreach ($conditions['rules'] as $rule) { |
||
280 | if (isset($rule['condition'])) { |
||
281 | $ruleResult = self::parseConditions($rule, $recordModel); |
||
282 | } else { |
||
283 | $ruleResult = self::checkCondition($rule, $recordModel); |
||
284 | } |
||
285 | if (!$andCondition && $ruleResult) { |
||
286 | $result = true; |
||
287 | break; |
||
288 | } |
||
289 | if ($andCondition && !$ruleResult) { |
||
290 | $result = false; |
||
291 | break; |
||
292 | } |
||
293 | $result = $ruleResult; |
||
294 | } |
||
295 | return $result; |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Check one condition. |
||
300 | * |
||
301 | * @param array $rule |
||
302 | * @param \Vtiger_Record_Model $recordModel |
||
303 | * |
||
304 | * @return bool |
||
305 | */ |
||
306 | public static function checkCondition(array $rule, \Vtiger_Record_Model $recordModel): bool |
||
307 | { |
||
308 | [$fieldName, $moduleName, $sourceFieldName] = array_pad(explode(':', $rule['fieldname']), 3, false); |
||
309 | if ($sourceFieldName) { |
||
310 | if ($recordModel->isEmpty($sourceFieldName)) { |
||
311 | return false; |
||
312 | } |
||
313 | $sourceRecordModel = self::$recordCache[$recordModel->get($sourceFieldName)] ?? \Vtiger_Record_Model::getInstanceById($recordModel->get($sourceFieldName)); |
||
314 | $fieldModel = $sourceRecordModel->getField($fieldName); |
||
315 | } elseif ($recordModel->getModuleName() === $moduleName) { |
||
316 | $fieldModel = $recordModel->getField($fieldName); |
||
317 | } |
||
318 | if (empty($fieldModel)) { |
||
319 | Log::error("Not found field model | Field name: $fieldName | Module: $moduleName", 'Condition'); |
||
320 | throw new \App\Exceptions\AppException('ERR_NOT_FOUND_FIELD_MODEL|' . $fieldName); |
||
321 | } |
||
322 | $className = '\App\Conditions\RecordFields\\' . ucfirst($fieldModel->getFieldDataType()) . 'Field'; |
||
323 | if (!class_exists($className)) { |
||
324 | Log::error("Not found record field condition | Field name: $fieldName | Module: $moduleName | FieldDataType: " . ucfirst($fieldModel->getFieldDataType()), 'Condition'); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
325 | throw new \App\Exceptions\AppException("ERR_NOT_FOUND_QUERY_FIELD_CONDITION|$fieldName|$className"); |
||
326 | } |
||
327 | if (!empty($sourceFieldName)) { |
||
328 | $recordField = new $className($sourceRecordModel, $fieldModel, $rule); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
329 | $recordField->setSource($recordModel, $sourceFieldName); |
||
330 | } else { |
||
331 | $recordField = new $className($recordModel, $fieldModel, $rule); |
||
332 | } |
||
333 | return $recordField->check(); |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Get field names from conditions. |
||
338 | * |
||
339 | * @param array $conditions |
||
340 | * |
||
341 | * @return array ['baseModule' => [], 'referenceModule' => []] |
||
342 | */ |
||
343 | public static function getFieldsFromConditions(array $conditions): array |
||
344 | { |
||
345 | $fields = ['baseModule' => [], 'referenceModule' => []]; |
||
346 | if (isset($conditions['rules'])) { |
||
347 | foreach ($conditions['rules'] as &$condition) { |
||
348 | if (isset($condition['condition'])) { |
||
349 | $fields = array_merge_recursive($fields, static::getFieldsFromConditions($condition)); |
||
350 | } else { |
||
351 | [$fieldName, $moduleName, $sourceFieldName] = array_pad(explode(':', $condition['fieldname']), 3, false); |
||
352 | if ($sourceFieldName) { |
||
353 | $fields['referenceModule'][$moduleName][$sourceFieldName] = $fieldName; |
||
354 | } else { |
||
355 | $fields['baseModule'][] = $fieldName; |
||
356 | } |
||
357 | } |
||
358 | } |
||
359 | } |
||
360 | return $fields; |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * Remove field from conditions. |
||
365 | * |
||
366 | * @param string $baseModuleName The base name of the module for which conditions have been set |
||
367 | * @param array $conditions |
||
368 | * @param string $moduleName The module name of the field to be deleted. |
||
369 | * @param string $fieldName The name of the field to be deleted |
||
370 | * |
||
371 | * @return array |
||
372 | */ |
||
373 | public static function removeFieldFromCondition(string $baseModuleName, array $conditions, string $moduleName, string $fieldName): array |
||
374 | { |
||
375 | if (isset($conditions['rules'])) { |
||
376 | foreach ($conditions['rules'] as $key => &$condition) { |
||
377 | if (isset($condition['condition'])) { |
||
378 | $condition = static::removeFieldFromCondition($baseModuleName, $condition, $moduleName, $fieldName); |
||
379 | } else { |
||
380 | [$cFieldName, $cModuleName, $sourceFieldName] = array_pad(explode(':', $condition['fieldname']), 3, false); |
||
381 | if (($fieldName === $cFieldName && $moduleName === $cModuleName) || ($sourceFieldName && $sourceFieldName === $fieldName && $moduleName === $baseModuleName)) { |
||
382 | $condition = []; |
||
383 | } |
||
384 | } |
||
385 | if (empty($condition)) { |
||
386 | unset($conditions['rules'][$key]); |
||
387 | } |
||
388 | } |
||
389 | if (empty($conditions['rules'] = array_filter($conditions['rules']))) { |
||
390 | $conditions = []; |
||
391 | } |
||
392 | } |
||
393 | return $conditions; |
||
394 | } |
||
395 | |||
396 | /** |
||
397 | * Checks structure advancedConditions. |
||
398 | * |
||
399 | * @param array $advancedConditions |
||
400 | * |
||
401 | * @return array |
||
402 | */ |
||
403 | public static function validAdvancedConditions(array $advancedConditions): array |
||
404 | { |
||
405 | if (!empty($advancedConditions['relationConditions']) && 0 != $advancedConditions['relationId']) { |
||
406 | $advancedConditions['relationConditions'] = self::getConditionsFromRequest($advancedConditions['relationConditions']); |
||
407 | } |
||
408 | if (!empty($advancedConditions['relationColumns'])) { |
||
409 | array_map(function ($v) { |
||
410 | if (!\App\Validator::integer($v)) { |
||
411 | throw new \App\Exceptions\IllegalValue('ERR_NOT_ALLOWED_VALUE||' . $v, 406); |
||
412 | } |
||
413 | }, $advancedConditions['relationColumns']); |
||
414 | } |
||
415 | return $advancedConditions; |
||
416 | } |
||
417 | } |
||
418 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.