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 | * Auto Assign file. |
||||||
4 | * |
||||||
5 | * The file is part of the paid functionality. Using the file is allowed only after purchasing a subscription. |
||||||
6 | * File modification allowed only with the consent of the system producer. |
||||||
7 | * |
||||||
8 | * @package App |
||||||
9 | * |
||||||
10 | * @copyright YetiForce S.A. |
||||||
11 | * @license YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com) |
||||||
12 | * @author Radosław Skrzypczak <[email protected]> |
||||||
13 | * @author Mariusz Krzaczkowski <[email protected]> |
||||||
14 | */ |
||||||
15 | |||||||
16 | namespace App; |
||||||
17 | |||||||
18 | /** |
||||||
19 | * Auto Assign class. |
||||||
20 | */ |
||||||
21 | class AutoAssign extends Base |
||||||
22 | { |
||||||
23 | /** @var string Basic table name */ |
||||||
24 | public const TABLE_NAME = 's_#__auto_assign'; |
||||||
25 | /** @var array Members tables */ |
||||||
26 | public const MEMBERS_TABLES = ['s_#__auto_assign_users' => 'id', 's_#__auto_assign_groups' => 'id', 's_#__auto_assign_roles' => 'id']; |
||||||
27 | /** @var string Round robin table name */ |
||||||
28 | public const ROUND_ROBIN_TABLE = 'u_#__auto_assign_rr'; |
||||||
29 | |||||||
30 | /** @var int Status inactive */ |
||||||
31 | public const STATUS_INACTIVE = 0; |
||||||
32 | /** @var int Status active */ |
||||||
33 | public const STATUS_ACTIVE = 1; |
||||||
34 | |||||||
35 | /** @var int Manual mode */ |
||||||
36 | public const MODE_MANUAL = 1; |
||||||
37 | /** @var int Handler mode */ |
||||||
38 | public const MODE_HANDLER = 2; |
||||||
39 | /** @var int Workflow mode */ |
||||||
40 | public const MODE_WORKFLOW = 4; |
||||||
41 | |||||||
42 | /** @var int Load balance method */ |
||||||
43 | public const METHOD_LOAD_BALANCE = 0; |
||||||
44 | /** @var int Round robin method */ |
||||||
45 | public const METHOD_ROUND_ROBIN = 1; |
||||||
46 | |||||||
47 | /** |
||||||
48 | * Get all auto assign entries for module. |
||||||
49 | * |
||||||
50 | * @param string $moduleName |
||||||
51 | * @param int $mode A bitmask of one or more of the mode flags |
||||||
52 | * @param int $state |
||||||
53 | * |
||||||
54 | * @return array |
||||||
55 | */ |
||||||
56 | public static function getByModule(string $moduleName, int $mode = self::MODE_HANDLER | self::MODE_WORKFLOW | self::MODE_MANUAL, int $state = self::STATUS_ACTIVE): array |
||||||
57 | { |
||||||
58 | $query = (new Db\Query())->from(self::TABLE_NAME) |
||||||
59 | ->where(['tabid' => Module::getModuleId($moduleName), 'state' => $state]); |
||||||
60 | $mods = ['or']; |
||||||
61 | foreach ([self::MODE_MANUAL => 'gui', self::MODE_HANDLER => 'handler', self::MODE_WORKFLOW => 'workflow'] as $key => $column) { |
||||||
62 | if ($mode & $key) { |
||||||
63 | $mods[] = [$column => 1]; |
||||||
64 | } |
||||||
65 | } |
||||||
66 | $query->andWhere($mods); |
||||||
67 | |||||||
68 | return $query->all(); |
||||||
69 | } |
||||||
70 | |||||||
71 | /** |
||||||
72 | * Get all auto assign instances for module. |
||||||
73 | * |
||||||
74 | * @param string $moduleName |
||||||
75 | * @param int|null $mode A bitmask of one or more of the mode flags |
||||||
76 | * |
||||||
77 | * @return array |
||||||
78 | */ |
||||||
79 | public static function getInstancesByModule(string $moduleName, int $mode = null): array |
||||||
80 | { |
||||||
81 | $instances = []; |
||||||
82 | foreach (self::getByModule($moduleName, $mode) as $autoAssignData) { |
||||||
83 | $instances[$autoAssignData['id']] = self::getInstance($autoAssignData); |
||||||
84 | } |
||||||
85 | return $instances; |
||||||
86 | } |
||||||
87 | |||||||
88 | /** |
||||||
89 | * Get auto assign instance for record. |
||||||
90 | * |
||||||
91 | * @param \Vtiger_Record_Model $recordModel |
||||||
92 | * @param int|null $mode A bitmask of one or more of the mode flags |
||||||
93 | * |
||||||
94 | * @return self|null |
||||||
95 | */ |
||||||
96 | public static function getAutoAssignForRecord(\Vtiger_Record_Model $recordModel, int $mode = null): ?self |
||||||
97 | { |
||||||
98 | $autoAssignInstance = null; |
||||||
99 | if (!\App\YetiForce\Shop::check('YetiForceAutoAssignment')) { |
||||||
100 | return $autoAssignInstance; |
||||||
101 | } |
||||||
102 | foreach (self::getByModule($recordModel->getModuleName(), $mode) as $autoAssignData) { |
||||||
103 | $conditions = Json::isEmpty($autoAssignData['conditions']) ? [] : Json::decode($autoAssignData['conditions']); |
||||||
104 | if (Condition::checkConditions($conditions, $recordModel)) { |
||||||
105 | $autoAssignInstance = self::getInstance($autoAssignData); |
||||||
106 | break; |
||||||
107 | } |
||||||
108 | } |
||||||
109 | return $autoAssignInstance; |
||||||
110 | } |
||||||
111 | |||||||
112 | /** |
||||||
113 | * Get auto assign instance by ID. |
||||||
114 | * |
||||||
115 | * @param int $id |
||||||
116 | * |
||||||
117 | * @return self|null |
||||||
118 | */ |
||||||
119 | public static function getInstanceById(int $id): ?self |
||||||
120 | { |
||||||
121 | $data = (new Db\Query())->from(self::TABLE_NAME)->where(['id' => $id])->one(); |
||||||
122 | return $data ? (new self())->setData($data) : null; |
||||||
123 | } |
||||||
124 | |||||||
125 | /** |
||||||
126 | * Get auto assign instance by data. |
||||||
127 | * |
||||||
128 | * @param array $data |
||||||
129 | * |
||||||
130 | * @return self|null |
||||||
131 | */ |
||||||
132 | public static function getInstance(array $data): ?self |
||||||
133 | { |
||||||
134 | return $data ? (new self())->setData($data) : null; |
||||||
135 | } |
||||||
136 | |||||||
137 | /** |
||||||
138 | * Function to get the Id. |
||||||
139 | * |
||||||
140 | * @return int |
||||||
141 | */ |
||||||
142 | public function getId(): int |
||||||
143 | { |
||||||
144 | return $this->get('id'); |
||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||
145 | } |
||||||
146 | |||||||
147 | /** |
||||||
148 | * Get name of auto assign instance. |
||||||
149 | * |
||||||
150 | * @param bool $encode |
||||||
151 | * |
||||||
152 | * @return string |
||||||
153 | */ |
||||||
154 | public function getName(bool $encode = true): string |
||||||
155 | { |
||||||
156 | return Language::translate($this->get('subject'), 'Settings:AutomaticAssignment', false, $encode); |
||||||
0 ignored issues
–
show
false of type false is incompatible with the type null|string expected by parameter $language of App\Language::translate() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
157 | } |
||||||
158 | |||||||
159 | /** |
||||||
160 | * Get module name. |
||||||
161 | * |
||||||
162 | * @return string |
||||||
163 | */ |
||||||
164 | public function getModuleName(): string |
||||||
165 | { |
||||||
166 | return Module::getModuleName($this->get('tabid')); |
||||||
0 ignored issues
–
show
|
|||||||
167 | } |
||||||
168 | |||||||
169 | /** |
||||||
170 | * Check conditions for record. |
||||||
171 | * |
||||||
172 | * @param \Vtiger_Record_Model $recordModel |
||||||
173 | * |
||||||
174 | * @return bool |
||||||
175 | */ |
||||||
176 | public function checkConditionForRecord(\Vtiger_Record_Model $recordModel): bool |
||||||
177 | { |
||||||
178 | $conditions = Json::isEmpty($this->get('conditions')) ? [] : Json::decode($this->get('conditions')); |
||||||
179 | return Condition::checkConditions($conditions, $recordModel); |
||||||
180 | } |
||||||
181 | |||||||
182 | /** |
||||||
183 | * Check if the instance is active in a given mode. |
||||||
184 | * |
||||||
185 | * @param int $mode |
||||||
186 | * |
||||||
187 | * @return bool |
||||||
188 | */ |
||||||
189 | public function isActive(int $mode): bool |
||||||
190 | { |
||||||
191 | switch ($mode) { |
||||||
192 | case self::MODE_MANUAL: |
||||||
193 | $result = !$this->isEmpty('gui'); |
||||||
194 | break; |
||||||
195 | case self::MODE_HANDLER: |
||||||
196 | $result = !$this->isEmpty('handler'); |
||||||
197 | break; |
||||||
198 | case self::MODE_WORKFLOW: |
||||||
199 | $result = !$this->isEmpty('workflow'); |
||||||
200 | break; |
||||||
201 | default: |
||||||
202 | $result = false; |
||||||
203 | break; |
||||||
204 | } |
||||||
205 | return $result && self::STATUS_ACTIVE === (int) $this->get('state'); |
||||||
206 | } |
||||||
207 | |||||||
208 | /** |
||||||
209 | * Get an automatic selected user ID. |
||||||
210 | * |
||||||
211 | * @return int |
||||||
212 | */ |
||||||
213 | public function getOwner(): ?int |
||||||
214 | { |
||||||
215 | switch ($this->get('method')) { |
||||||
216 | case self::METHOD_LOAD_BALANCE: |
||||||
217 | $owner = $this->getQueryByLoadBalance()->scalar() ?: null; |
||||||
218 | break; |
||||||
219 | case self::METHOD_ROUND_ROBIN: |
||||||
220 | $owner = $this->getQueryByRoundRobin()->scalar() ?: null; |
||||||
221 | break; |
||||||
222 | default: |
||||||
223 | $owner = null; |
||||||
224 | break; |
||||||
225 | } |
||||||
226 | |||||||
227 | return $owner ? $owner : $this->getDefaultOwner(); |
||||||
0 ignored issues
–
show
|
|||||||
228 | } |
||||||
229 | |||||||
230 | /** |
||||||
231 | * Get automatic selected users. |
||||||
232 | * |
||||||
233 | * @return array |
||||||
234 | */ |
||||||
235 | public function getOwners(): array |
||||||
236 | { |
||||||
237 | switch ($this->get('method')) { |
||||||
238 | case self::METHOD_LOAD_BALANCE: |
||||||
239 | $owner = $this->getQueryByLoadBalance()->all(); |
||||||
240 | break; |
||||||
241 | case self::METHOD_ROUND_ROBIN: |
||||||
242 | $owner = $this->getQueryByRoundRobin()->all(); |
||||||
243 | break; |
||||||
244 | default: |
||||||
245 | $owner = []; |
||||||
246 | break; |
||||||
247 | } |
||||||
248 | |||||||
249 | return $owner; |
||||||
250 | } |
||||||
251 | |||||||
252 | /** |
||||||
253 | * Get default owner. |
||||||
254 | * |
||||||
255 | * @return int |
||||||
256 | */ |
||||||
257 | public function getDefaultOwner(): ?int |
||||||
258 | { |
||||||
259 | $owner = null; |
||||||
260 | $defaultOwner = (int) $this->get('default_assign'); |
||||||
261 | $ownerModel = Fields\Owner::getInstance($this->getModuleName()); |
||||||
262 | |||||||
263 | $type = $defaultOwner ? Fields\Owner::getType($defaultOwner) : null; |
||||||
264 | if ('Users' === $type) { |
||||||
0 ignored issues
–
show
|
|||||||
265 | $owner = User::isExists($defaultOwner) ? $defaultOwner : $owner; |
||||||
266 | } elseif ($type) { |
||||||
267 | $owner = \array_key_exists($defaultOwner, $ownerModel->getAccessibleGroupForModule()) ? $defaultOwner : $owner; |
||||||
268 | } |
||||||
269 | |||||||
270 | return $owner; |
||||||
271 | } |
||||||
272 | |||||||
273 | /** |
||||||
274 | * Query object for users allowed to be assigned by load balanced method. |
||||||
275 | * |
||||||
276 | * In order to correctly balance the entries attribution |
||||||
277 | * we need ot randomize the order in which they are returned. |
||||||
278 | * Otherwise, when multiple users have the same amount of entries |
||||||
279 | * it is always the first one in the results who will be assigned to new entry. |
||||||
280 | * |
||||||
281 | * @return Db\Query |
||||||
282 | */ |
||||||
283 | public function getQueryByLoadBalance(): Db\Query |
||||||
284 | { |
||||||
285 | return $this->getQuery()->orderBy(['count' => SORT_ASC, new \yii\db\Expression('RAND()')]); |
||||||
286 | } |
||||||
287 | |||||||
288 | /** |
||||||
289 | * Query object for users allowed to be assigned by round robin. |
||||||
290 | * |
||||||
291 | * @return Db\Query |
||||||
292 | */ |
||||||
293 | public function getQueryByRoundRobin(): Db\Query |
||||||
294 | { |
||||||
295 | $robinTable = self::ROUND_ROBIN_TABLE; |
||||||
296 | $columnName = "{$robinTable}.datetime"; |
||||||
297 | $id = $this->getId(); |
||||||
298 | |||||||
299 | return $this->getQuery()->leftJoin($robinTable, "vtiger_users.id = {$robinTable}.user AND {$robinTable}.id={$id}") |
||||||
300 | ->addSelect([$columnName]) |
||||||
301 | ->addGroupBy($columnName) |
||||||
302 | ->orderBy([$columnName => SORT_ASC]); |
||||||
303 | } |
||||||
304 | |||||||
305 | /** |
||||||
306 | * Query object for users allowed for assignment. |
||||||
307 | * |
||||||
308 | * @return Db\Query |
||||||
309 | */ |
||||||
310 | public function getQuery(): Db\Query |
||||||
311 | { |
||||||
312 | $ownerFieldName = 'assigned_user_id'; |
||||||
313 | $queryGeneratorUsers = $this->getAvailableUsersQuery(); |
||||||
314 | |||||||
315 | $queryGenerator = (new QueryGenerator($this->getModuleName())); |
||||||
316 | $queryGenerator->permissions = false; |
||||||
317 | $conditions = Json::isEmpty($this->get('record_limit_conditions')) ? [] : Json::decode($this->get('record_limit_conditions')); |
||||||
318 | $queryGenerator->setFields([$ownerFieldName]) |
||||||
319 | ->setCustomColumn(['count' => new \yii\db\Expression('COUNT(*)')]) |
||||||
320 | ->setConditions($conditions) |
||||||
321 | ->setGroup($ownerFieldName) |
||||||
322 | ->addNativeCondition([$queryGenerator->getColumnName($ownerFieldName) => $queryGeneratorUsers->createQuery()]); |
||||||
323 | $subQuery = $queryGenerator->createQuery(); |
||||||
324 | |||||||
325 | $recordLimit = (int) $this->get('record_limit'); |
||||||
326 | if (0 === $recordLimit) { |
||||||
327 | $queryGeneratorUsers->setCustomColumn(['temp_limit' => $queryGeneratorUsers->getColumnName('records_limit')]); |
||||||
328 | } else { |
||||||
329 | $queryGeneratorUsers->setCustomColumn(['temp_limit' => new \yii\db\Expression($recordLimit)]); |
||||||
330 | } |
||||||
331 | $queryGeneratorUsers->setGroup('id')->setCustomGroup(['temp_limit', 'count']); |
||||||
332 | $query = $queryGeneratorUsers->createQuery(true); |
||||||
333 | $query->leftJoin(['crm_data_temp_table' => $subQuery], "crm_data_temp_table.{$ownerFieldName}={$queryGeneratorUsers->getColumnName('id')}"); |
||||||
334 | $query->addSelect(['crm_data_temp_table.count']); |
||||||
335 | $query->andHaving(['or', ['<', 'count', new \yii\db\Expression('temp_limit')], ['temp_limit' => 0], ['count' => null]]); |
||||||
336 | |||||||
337 | return $query; |
||||||
338 | } |
||||||
339 | |||||||
340 | /** |
||||||
341 | * Query generator object of available users. |
||||||
342 | * |
||||||
343 | * @return QueryGenerator |
||||||
344 | */ |
||||||
345 | public function getAvailableUsersQuery(): QueryGenerator |
||||||
346 | { |
||||||
347 | $queryGenerator = (new QueryGenerator('Users')) |
||||||
348 | ->setFields(['id']) |
||||||
349 | ->addCondition('status', 'Active', 'e') |
||||||
350 | ->addCondition('available', 1, 'e') |
||||||
351 | ->addCondition('auto_assign', 1, 'e'); |
||||||
352 | $columnName = $queryGenerator->getColumnName('id'); |
||||||
353 | |||||||
354 | $condition = ['or']; |
||||||
355 | foreach ($this->getMembers() as $member) { |
||||||
356 | [$type, $id] = explode(':', $member); |
||||||
357 | switch ($type) { |
||||||
358 | case PrivilegeUtil::MEMBER_TYPE_USERS: |
||||||
359 | $condition[$type][$columnName][] = (int) $id; |
||||||
360 | break; |
||||||
361 | case PrivilegeUtil::MEMBER_TYPE_GROUPS: |
||||||
362 | $condition[] = [$columnName => (new Db\Query())->select(['userid'])->from(["condition_{$type}_{$id}_" . Layout::getUniqueId() => PrivilegeUtil::getQueryToUsersByGroup((int) $id)])]; |
||||||
363 | break; |
||||||
364 | case PrivilegeUtil::MEMBER_TYPE_ROLES: |
||||||
365 | $condition[] = [$columnName => PrivilegeUtil::getQueryToUsersByRole($id)]; |
||||||
366 | break; |
||||||
367 | case PrivilegeUtil::MEMBER_TYPE_ROLE_AND_SUBORDINATES: |
||||||
368 | $condition[] = [$columnName => PrivilegeUtil::getQueryToUsersByRoleAndSubordinate($id)]; |
||||||
369 | break; |
||||||
370 | default: |
||||||
371 | break; |
||||||
372 | } |
||||||
373 | } |
||||||
374 | if (1 === \count($condition)) { |
||||||
375 | $condition = [$columnName => 0]; |
||||||
376 | } |
||||||
377 | $queryGenerator->addNativeCondition($condition); |
||||||
378 | |||||||
379 | return $queryGenerator; |
||||||
380 | } |
||||||
381 | |||||||
382 | /** |
||||||
383 | * Get members. |
||||||
384 | * |
||||||
385 | * @return array |
||||||
386 | */ |
||||||
387 | public function getMembers(): array |
||||||
388 | { |
||||||
389 | if (!$this->has('members')) { |
||||||
390 | $queryAll = null; |
||||||
391 | foreach (self::MEMBERS_TABLES as $tableName => $index) { |
||||||
392 | $query = (new Db\Query()) |
||||||
393 | ->select(['member' => new \yii\db\Expression('CONCAT(type,\':\', member)')]) |
||||||
394 | ->from($tableName) |
||||||
395 | ->where(["{$tableName}.{$index}" => $this->getId()]); |
||||||
396 | if ($queryAll) { |
||||||
397 | $queryAll->union($query, true); |
||||||
398 | } else { |
||||||
399 | $queryAll = $query; |
||||||
400 | } |
||||||
401 | } |
||||||
402 | $members = $queryAll->column(); |
||||||
0 ignored issues
–
show
The method
column() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
403 | $this->set('members', $members); |
||||||
404 | } |
||||||
405 | return $this->get('members'); |
||||||
0 ignored issues
–
show
|
|||||||
406 | } |
||||||
407 | |||||||
408 | /** |
||||||
409 | * Post process action. |
||||||
410 | * |
||||||
411 | * @param int $userId |
||||||
412 | * |
||||||
413 | * @return void |
||||||
414 | */ |
||||||
415 | public function postProcess(int $userId) |
||||||
416 | { |
||||||
417 | $dbCommand = Db::getInstance()->createCommand(); |
||||||
418 | if ($userId && self::METHOD_ROUND_ROBIN === (int) $this->get('method')) { |
||||||
419 | $params = ['id' => $this->getId(), 'user' => $userId]; |
||||||
420 | $isExists = (new Db\Query())->from(self::ROUND_ROBIN_TABLE)->where($params)->exists(); |
||||||
421 | if ($isExists) { |
||||||
422 | $dbCommand->update(self::ROUND_ROBIN_TABLE, ['datetime' => (new \DateTime())->format('Y-m-d H:i:s.u')], $params)->execute(); |
||||||
423 | } elseif (\App\User::isExists($userId, false)) { |
||||||
424 | $params['datetime'] = (new \DateTime())->format('Y-m-d H:i:s.u'); |
||||||
425 | $dbCommand->insert(self::ROUND_ROBIN_TABLE, $params)->execute(); |
||||||
426 | } |
||||||
427 | } |
||||||
428 | } |
||||||
429 | } |
||||||
430 |