Passed
Pull Request — developer (#16600)
by Arkadiusz
16:09
created

Vtiger_Relation_Model::setHandlerExceptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 3
rs 10
ccs 0
cts 0
cp 0
cc 1
nc 1
nop 1
crap 2
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 Vtiger_Relation_Model extends \App\Base
13
{
14
	/**
15
	 *  Cached instances.
16
	 *
17
	 * @var Vtiger_Relation_Model[]
18
	 */
19
	protected static $cachedInstances = [];
20
	/**
21
	 * Cached instances by relation id.
22
	 *
23
	 * @var Vtiger_Relation_Model[]
24
	 */
25
	protected static $cachedInstancesById = [];
26
	protected $parentModule = false;
27
	protected $relatedModule = false;
28
	/**
29
	 * @var \App\Relation\RelationAbstraction Class that includes basic operations on relations
30
	 */
31
	protected $typeRelationModel;
32
	/**
33
	 * @var array Custom view list
34
	 */
35 1
	protected $customViewList;
36
37 1
	/**
38
	 * @var array Event handler exceptions
39
	 */
40
	protected $handlerExceptions = [];
41
42
	//one to many
43
	const RELATION_O2M = 1;
44
45
	//Many to many and many to one
46
	const RELATION_M2M = 2;
47 4
48
	/**
49 4
	 * Function returns the relation id.
50
	 *
51 4
	 * @return int
52
	 */
53
	public function getId()
54
	{
55
		return $this->get('relation_id');
56
	}
57
58
	/**
59 2
	 * Function sets the relation's parent module model.
60
	 *
61 2
	 * @param Vtiger_Module_Model $moduleModel
62
	 *
63
	 * @return Vtiger_Relation_Model
64 2
	 */
65
	public function setParentModuleModel($moduleModel)
66
	{
67
		$this->parentModule = $moduleModel;
68
		return $this;
69
	}
70
71
	/**
72
	 * Function that returns the relation's parent module model.
73
	 *
74 2
	 * @return Vtiger_Module_Model
75
	 */
76 2
	public function getParentModuleModel()
77
	{
78 2
		if (empty($this->parentModule)) {
79
			$this->parentModule = Vtiger_Module_Model::getInstance($this->get('tabid'));
80
		}
81
		return $this->parentModule;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parentModule also could return the type boolean which is incompatible with the documented return type Vtiger_Module_Model.
Loading history...
82
	}
83
84
	/**
85
	 * Gets parent record model.
86 3
	 *
87
	 * @return Vtiger_Record_Model|null
88 3
	 */
89 1
	public function getParentRecord(): ?Vtiger_Record_Model
90
	{
91 3
		return $this->get('parentRecord') ?? null;
92
	}
93
94
	/**
95
	 * Set relation's parent module model.
96
	 *
97
	 * @param Vtiger_Module_Model $relationModel
98
	 *
99 1
	 * @return $this
100
	 */
101 1
	public function setRelationModuleModel($relationModel)
102 1
	{
103
		$this->relatedModule = $relationModel;
104
		return $this;
105 1
	}
106
107
	/**
108
	 * Function that returns the relation's related module model.
109
	 *
110
	 * @return Vtiger_Module_Model
111
	 */
112
	public function getRelationModuleModel()
113
	{
114
		if (!$this->relatedModule) {
115
			$this->relatedModule = Vtiger_Module_Model::getInstance($this->get('related_tabid'));
116
		}
117
		return $this->relatedModule;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->relatedModule also could return the type true which is incompatible with the documented return type Vtiger_Module_Model.
Loading history...
118
	}
119
120
	/**
121
	 * Get relation module name.
122
	 *
123
	 * @return string
124
	 */
125
	public function getRelationModuleName()
126
	{
127
		$relationModuleName = $this->get('relatedModuleName');
128
		if (!empty($relationModuleName)) {
129
			return $relationModuleName;
130
		}
131
		return $this->getRelationModuleModel()->getName();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getRelationModuleModel()->getName() returns the type boolean which is incompatible with the documented return type string.
Loading history...
132
	}
133
134
	/**
135
	 * Get actions.
136
	 *
137
	 * @return string[]
138
	 */
139
	public function getActions()
140
	{
141
		if (\is_array($this->get('actions'))) {
142
			return $this->get('actions');
143
		}
144
		// No actions for Activity history
145
		if ('Activity History' === $this->get('c')) {
146
			return [];
147
		}
148
		$actions = explode(',', strtolower($this->get('actions')));
149
		$this->set('actions', $actions);
150
		return $actions;
151
	}
152
153
	/**
154
	 * Check if action is supported.
155
	 *
156
	 * @param string $actionName
157
	 *
158
	 * @return bool
159
	 */
160
	public function isActionSupported($actionName)
161
	{
162
		return \in_array(strtolower($actionName), $this->getActions());
163
	}
164
165 2
	/**
166
	 * Is record selection action available.
167 2
	 *
168 2
	 * @return bool
169
	 */
170
	public function isSelectActionSupported()
171
	{
172
		return $this->isActionSupported('select');
173
	}
174
175
	/**
176
	 * Is record add action available.
177
	 *
178 2
	 * @return bool
179
	 */
180 2
	public function isAddActionSupported()
181 2
	{
182
		return $this->isActionSupported('add') && $this->getRelationModuleModel()->isPermitted('CreateView');
183
	}
184
185
	/**
186
	 * Check favorite.
187
	 */
188
	public function isFavorites()
189
	{
190
		return 1 == $this->get('favorites') ? true : false;
191 2
	}
192
193 2
	/**
194 1
	 * Check related view type.
195
	 *
196 2
	 * @param string $type
197
	 *
198
	 * @return bool
199
	 */
200
	public function isRelatedViewType($type)
201
	{
202
		return false !== strpos($this->get('view_type'), $type);
203
	}
204
205
	/**
206
	 * Show user who created relation.
207
	 *
208
	 * @return bool
209
	 */
210
	public function showCreatorDetail()
211
	{
212
		if (0 === $this->get('creator_detail') || self::RELATION_M2M !== $this->getRelationType()) {
213
			return false;
214
		}
215
		return (bool) $this->get('creator_detail');
216
	}
217
218
	/**
219
	 * Show comments in related module.
220
	 *
221
	 * @return bool
222
	 */
223
	public function showComment()
224
	{
225
		if (0 === $this->get('relation_comment') || self::RELATION_M2M !== $this->getRelationType()) {
226
			return false;
227
		}
228
		return (bool) $this->get('relation_comment');
229
	}
230
231
	/**
232
	 * Get query generator instance.
233
	 *
234
	 * @return \App\QueryGenerator
235
	 */
236
	public function getQueryGenerator(): App\QueryGenerator
237
	{
238
		if (!$this->has('query_generator')) {
239
			$this->set('query_generator', new \App\QueryGenerator($this->getRelationModuleName()));
240
		}
241
		return $this->get('query_generator');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get('query_generator') could return the type null which is incompatible with the type-hinted return App\QueryGenerator. Consider adding an additional type-check to rule them out.
Loading history...
242
	}
243
244
	/**
245
	 * Get relation type.
246
	 *
247 2
	 * @return int
248
	 */
249 2
	public function getRelationType()
250 2
	{
251 2
		if (!$this->has('relationType')) {
252
			$this->set('relationType', $this->getTypeRelationModel()->getRelationType());
253 2
		}
254
		return $this->get('relationType');
255
	}
256
257
	/**
258
	 * Get related view type.
259
	 *
260
	 * @return string[]
261
	 */
262
	public function getRelatedViewType(): array
263
	{
264
		return explode(',', $this->get('view_type')) ?? [];
265 2
	}
266 2
267 2
	/**
268 2
	 * Get custom view.
269 2
	 *
270 2
	 * @return string[]
271
	 */
272
	public function getCustomView(): array
273 2
	{
274 2
		if ($this->isEmpty('custom_view')) {
275 2
			return [];
276 2
		}
277 2
		return explode(',', $this->get('custom_view')) ?? [];
278 2
	}
279
280 2
	/**
281
	 * Get relation model instance.
282 1
	 *
283
	 * @param Vtiger_Module_Model $parentModuleModel
284
	 * @param Vtiger_Module_Model $relatedModuleModel
285
	 * @param bool|int            $relationId
286
	 *
287
	 * @return $this|bool
288 2
	 */
289
	public static function getInstance($parentModuleModel, $relatedModuleModel, $relationId = false)
290 2
	{
291 2
		$relKey = $parentModuleModel->getId() . '_' . $relatedModuleModel->getId() . '_' . $relationId;
292 2
		if (isset(self::$cachedInstances[$relKey])) {
293 2
			return self::$cachedInstances[$relKey] ? clone self::$cachedInstances[$relKey] : self::$cachedInstances[$relKey];
294
		}
295
		if (('ModComments' == $relatedModuleModel->getName() && $parentModuleModel->isCommentEnabled()) || 'Documents' == $parentModuleModel->getName()) {
296 2
			$moduleName = 'ModComments' == $relatedModuleModel->getName() ? $relatedModuleModel->getName() : $parentModuleModel->getName();
297 2
			$relationModelClassName = Vtiger_Loader::getComponentClassName('Model', 'Relation', $moduleName);
298 2
			$relationModel = new $relationModelClassName();
299
			$relationModel->setParentModuleModel($parentModuleModel)->setRelationModuleModel($relatedModuleModel);
300
			if (method_exists($relationModel, 'setExceptionData')) {
301 2
				$relationModel->setExceptionData();
302
			}
303
			self::$cachedInstances[$relKey] = $relationModel;
304
			return clone $relationModel;
305
		}
306
		if (empty($relationId)) {
307
			$row = current(\App\Relation::getByModule($parentModuleModel->getName(), true, $relatedModuleModel->getName()));
0 ignored issues
show
Bug introduced by
$relatedModuleModel->getName() of type boolean is incompatible with the type null|string expected by parameter $relatedModuleName of App\Relation::getByModule(). ( Ignorable by Annotation )

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

307
			$row = current(\App\Relation::getByModule($parentModuleModel->getName(), true, /** @scrutinizer ignore-type */ $relatedModuleModel->getName()));
Loading history...
Bug introduced by
$parentModuleModel->getName() of type boolean is incompatible with the type string expected by parameter $moduleName of App\Relation::getByModule(). ( Ignorable by Annotation )

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

307
			$row = current(\App\Relation::getByModule(/** @scrutinizer ignore-type */ $parentModuleModel->getName(), true, $relatedModuleModel->getName()));
Loading history...
308
		} else {
309
			$row = \App\Relation::getById($relationId);
310
			if (1 === $row['presence'] || $row['tabid'] !== $parentModuleModel->getId() || $row['related_tabid'] !== $relatedModuleModel->getId()) {
311 2
				$row = [];
312
			}
313 2
		}
314
		if ($row) {
315
			$row['modulename'] = $row['related_modulename'];
316
			$relationModelClassName = Vtiger_Loader::getComponentClassName('Model', 'Relation', $parentModuleModel->get('name'));
317 2
			$relationModel = new $relationModelClassName();
318
			$relationModel->setData($row)->setParentModuleModel($parentModuleModel)->setRelationModuleModel($relatedModuleModel);
319
			$relationModel->set('relatedModuleName', $row['related_modulename']);
320
			self::$cachedInstances[$relKey] = $relationModel;
321
			self::$cachedInstancesById[$row['relation_id']] = $relationModel;
322 2
			return clone $relationModel;
323 2
		}
324 2
		self::$cachedInstances[$relKey] = false;
325
		return false;
326 2
	}
327
328
	/**
329
	 * Get relation model instance by relation id.
330 2
	 *
331
	 * @param int $relationId
332
	 *
333 2
	 * @return self|bool
334 2
	 */
335 2
	public static function getInstanceById(int $relationId)
336
	{
337 2
		if (!isset(self::$cachedInstancesById[$relationId])) {
338
			$row = \App\Relation::getById($relationId);
339
			$relationModel = false;
340
			if ($row) {
341
				$row['modulename'] = $row['related_modulename'];
342
				$parentModuleModel = Vtiger_Module_Model::getInstance($row['tabid']);
343
				$relationModelClassName = Vtiger_Loader::getComponentClassName('Model', 'Relation', $parentModuleModel->getName());
344
				$relationModel = new $relationModelClassName();
345 2
				$relationModel->setData($row)->setParentModuleModel($parentModuleModel)->setRelationModuleModel(Vtiger_Module_Model::getInstance($row['related_modulename']));
346
				$relationModel->set('relatedModuleName', $row['related_modulename']);
347 2
				if (method_exists($relationModel, 'setExceptionData')) {
348 2
					$relationModel->setExceptionData();
349
				}
350 1
			}
351 1
			self::$cachedInstancesById[$relationId] = $relationModel;
352
		}
353 1
		return self::$cachedInstancesById[$relationId] ? clone self::$cachedInstancesById[$relationId] : null;
354
	}
355
356 1
	/**
357
	 * Get type relation model.
358
	 *
359
	 * @return \App\Relation\RelationAbstraction
360
	 */
361 1
	public function getTypeRelationModel(): App\Relation\RelationAbstraction
362 1
	{
363 1
		if (!isset($this->typeRelationModel)) {
364
			$name = ucfirst($this->get('name'));
365
			$relationClassName = Vtiger_Loader::getComponentClassName('Relation', $name, $this->getParentModuleModel()->getName(), false);
366
			if (!$relationClassName) {
367
				$relationClassName = Vtiger_Loader::getComponentClassName('Relation', $name, $this->getRelationModuleName());
368
			}
369
			if (class_exists($relationClassName)) {
370 1
				$this->typeRelationModel = new $relationClassName();
371 1
				$this->typeRelationModel->relationModel = &$this;
372 1
			} else {
373 1
				App\Log::error("Not exist relation: {$name} in " . __METHOD__);
374
				throw new \App\Exceptions\NotAllowedMethod('LBL_NOT_EXIST_RELATION: ' . $name);
375
			}
376 1
		}
377
		return $this->typeRelationModel;
378 1
	}
379 1
380
	/**
381 1
	 * Get query form relation.
382
	 *
383
	 * @throws \App\Exceptions\NotAllowedMethod
384
	 *
385
	 * @return \App\QueryGenerator
386
	 */
387
	public function getQuery()
388
	{
389
		if (empty($this->get('parentRecord'))) {
390
			App\Log::error('No value parentRecord in ' . __METHOD__);
391
			throw new \App\Exceptions\IllegalValue('ERR_NO_VALUE||parentRecord');
392
		}
393 2
		$queryGenerator = $this->getQueryGenerator();
394
		$queryGenerator->setSourceRecord($this->get('parentRecord')->getId());
395 2
		$this->getTypeRelationModel()->getQuery();
396 2
		if ($this->showCreatorDetail()) {
397
			$queryGenerator->setCustomColumn('rel_created_user');
398 2
			$queryGenerator->setCustomColumn('rel_created_time');
399 2
		}
400 2
		if ($this->showComment()) {
401 2
			$queryGenerator->setCustomColumn('rel_comment');
402 2
		}
403 2
		$fields = array_keys($this->getQueryFields());
404 2
		$fields[] = 'id';
405 2
		$queryGenerator->setFields($fields);
406 2
		return $queryGenerator;
407 2
	}
408
409
	/**
410
	 * Get query fields.
411 2
	 *
412 1
	 * @return Vtiger_Field_Model[] with field name as key
413 1
	 */
414 1
	public function getQueryFields()
415 1
	{
416 1
		if ($this->has('QueryFields')) {
417
			return $this->get('QueryFields');
418
		}
419
		$relatedListFields = [];
420
		$relatedModuleModel = $this->getRelationModuleModel();
421
		// Get fields from panel
422
		foreach (App\Field::getFieldsFromRelation($this->getId()) as $fieldName) {
423 2
			$relatedListFields[$fieldName] = $relatedModuleModel->getFieldByName($fieldName);
424
		}
425 2
		if ($relatedListFields) {
426
			$this->set('QueryFields', $relatedListFields);
427
			return $relatedListFields;
428
		}
429
		$queryGenerator = $this->getQueryGenerator();
430
		$entity = $queryGenerator->getEntityModel();
431
		if (!empty($entity->relationFields)) {
432
			// Get fields from entity model
433
			foreach ($entity->relationFields as $fieldName) {
434
				$relatedListFields[$fieldName] = $relatedModuleModel->getFieldByName($fieldName);
435
			}
436
		} else {
437
			// Get fields from default CustomView
438
			$queryGenerator->initForDefaultCustomView(true, true);
439
			foreach ($queryGenerator->getFields() as $fieldName) {
440
				if ('id' !== $fieldName) {
441
					$relatedListFields[$fieldName] = $relatedModuleModel->getFieldByName($fieldName);
442
				}
443
			}
444
			$relatedListFields['id'] = true;
445
		}
446
		if ($relatedListFields) {
447
			$this->set('QueryFields', $relatedListFields);
448
			return $relatedListFields;
449
		}
450
		$this->set('QueryFields', $relatedListFields);
451
		return $relatedListFields;
452
	}
453
454
	/**
455
	 * Function to get relation field for relation module and parent module.
456
	 *
457
	 * @return Vtiger_Field_Model
458
	 */
459
	public function getRelationField()
460
	{
461
		if ($this->has('RelationField')) {
462
			return $this->get('RelationField');
463
		}
464
		$relatedModuleModel = $this->getRelationModuleModel();
465
		$parentModuleName = $this->getParentModuleModel()->getName();
466
		$relatedModelFields = $relatedModuleModel->getFields();
467
		if (!$this->isEmpty('field_name') && isset($relatedModelFields[$this->get('field_name')])) {
468
			$relationField = $relatedModelFields[$this->get('field_name')];
469
		} else {
470
			$fieldRel = App\Field::getRelatedFieldForModule($relatedModuleModel->getName(), $parentModuleName);
471
			if (isset($fieldRel['fieldid'])) {
472
				foreach ($relatedModelFields as $fieldModel) {
473
					if ($fieldModel->getId() === $fieldRel['fieldid']) {
474
						$relationField = $fieldModel;
475
						break;
476
					}
477
				}
478
			}
479
		}
480
		if (empty($relationField)) {
481
			$relationField = false;
482
			foreach ($relatedModelFields as $fieldModel) {
483
				if ($fieldModel->isReferenceField()) {
484
					$referenceList = $fieldModel->getReferenceList();
485
					if (!empty($referenceList) && \in_array($parentModuleName, $referenceList)) {
486
						$relationField = $fieldModel;
487
						break;
488
					}
489
				}
490
			}
491
		}
492
		$this->set('RelationField', $relationField ?: false);
493
		return $relationField;
494
	}
495
496
	/**
497
	 * Get relation inventory fields.
498
	 *
499
	 * @return Vtiger_Basic_InventoryField[]
500
	 */
501
	public function getRelationInventoryFields()
502
	{
503
		if (!$this->has('RelationInventoryFields')) {
504
			$this->set('RelationInventoryFields', []);
505
			if ($this->getRelationModuleModel()->isInventory()) {
506
				$columns = (new \App\Db\Query())
507
					->select(['fieldname'])
508
					->from('a_#__relatedlists_inv_fields')
509
					->where(['relation_id' => $this->getId()])
510
					->orderBy('sequence')
511
					->column();
512
				$inventoryFields = Vtiger_Inventory_Model::getInstance($this->getRelationModuleModel()->getName())->getFields();
0 ignored issues
show
Bug introduced by
$this->getRelationModuleModel()->getName() of type boolean is incompatible with the type string expected by parameter $moduleName of Vtiger_Inventory_Model::getInstance(). ( Ignorable by Annotation )

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

512
				$inventoryFields = Vtiger_Inventory_Model::getInstance(/** @scrutinizer ignore-type */ $this->getRelationModuleModel()->getName())->getFields();
Loading history...
513
				$fields = [];
514
				foreach ($columns as &$column) {
515
					if (!empty($inventoryFields[$column]) && $inventoryFields[$column]->isVisible()) {
516
						$fields[$column] = $inventoryFields[$column];
517
					}
518
				}
519
				$this->set('RelationInventoryFields', $fields);
520
			}
521
		}
522
		return $this->get('RelationInventoryFields');
523
	}
524
525
	/**
526
	 * Function which will specify whether the relation is editable.
527
	 *
528
	 * @return bool
529
	 */
530
	public function isEditable(): bool
531
	{
532
		return $this->getRelationModuleModel()->isPermitted('EditView');
533
	}
534
535
	/**
536
	 * Function which will specify whether the relation is deletable.
537
	 *
538
	 * @param \Vtiger_Record_Model|null $recordModel
539
	 * @param int|null                  $recordId
540
	 *
541
	 * @return bool
542
	 */
543
	public function privilegeToDelete(Vtiger_Record_Model $recordModel = null, int $recordId = null): bool
544
	{
545
		$returnVal = $this->getRelationModuleModel()->isPermitted('RemoveRelation');
546
		if ($returnVal && $this->getRelationType() === static::RELATION_O2M && ($fieldModel = $this->getRelationField())) {
547
			if (!$recordModel && $recordId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $recordId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
548
				$recordModel = \Vtiger_Record_Model::getInstanceById($recordId);
549
			}
550
			$returnVal = !$fieldModel->isMandatory() && $fieldModel->isEditable() && !$fieldModel->isEditableReadOnly() && (!$recordModel || $recordModel->isEditable());
551
		}
552
		return $returnVal;
553
	}
554
555
	/**
556
	 * Function which will specify whether the tree element is deletable.
557
	 *
558
	 * @return bool
559
	 */
560
	public function privilegeToTreeDelete(): bool
561
	{
562
		return $this->getRelationModuleModel()->isPermitted('RemoveRelation');
563
	}
564
565
	/**
566
	 * Get list url for record.
567
	 *
568
	 * @param Vtiger_Module_Model $parentRecordModel
569
	 *
570
	 * @return string
571
	 */
572
	public function getListUrl(Vtiger_Record_Model $parentRecordModel): string
573
	{
574
		$url = 'index.php?module=' . $this->getParentModuleModel()->get('name') . '&relatedModule=' . $this->get('modulename') .
575
			'&view=Detail&record=' . $parentRecordModel->getId() . '&mode=showRelatedList&relationId=' . $this->getId();
576
		if ('Calendar' == $this->get('modulename')) {
577
			$url .= '&time=current';
578
		}
579
		return $url;
580
	}
581
582
	/**
583
	 * Get create url from parent record.
584
	 *
585
	 * @param bool $fullView
586
	 *
587
	 * @return string
588
	 */
589
	public function getCreateViewUrl(bool $fullView = false)
590
	{
591
		$parentRecord = $this->getParentRecord();
592
		$relatedModuleModel = $this->getRelationModuleModel();
593
		if (!$fullView && $relatedModuleModel->isQuickCreateSupported()) {
594
			$createViewUrl = $relatedModuleModel->getQuickCreateUrl();
595
		} else {
596
			$createViewUrl = $relatedModuleModel->getCreateRecordUrl();
597
		}
598
		$createViewUrl .= '&sourceModule=' . $parentRecord->getModule()->getName() . '&sourceRecord=' . $parentRecord->getId() . '&relationOperation=true&relationId=' . $this->getId();
599
		if ($this->isDirectRelation()) {
600
			$relationField = $this->getRelationField();
601
			$createViewUrl .= '&' . $relationField->getName() . '=' . $parentRecord->getId();
602
		}
603
604
		return $createViewUrl;
605
	}
606
607
	/**
608
	 * Get delete url from parent record.
609
	 *
610
	 * @param int $relatedRecordId
611
	 *
612
	 * @return string
613
	 */
614
	public function getDeleteUrl(int $relatedRecordId)
615
	{
616
		$parentModuleName = $this->getParentModuleModel()->getName();
617
		$relatedModuleName = $this->getRelationModuleModel()->getName();
618
		$recordId = $this->getParentRecord()->getId();
619
620
		return "index.php?module={$parentModuleName}&related_module={$relatedModuleName}&action=RelationAjax&mode=deleteRelation&related_record_list=[{$relatedRecordId}]&src_record={$recordId}&relationId={$this->getId()}";
621
	}
622
623
	/**
624
	 * Add relation.
625
	 *
626
	 * @param int       $sourceRecordId
627
	 * @param int|int[] $destinationRecordIds
628
	 * @param mixed     $params
629
	 */
630
	public function addRelation($sourceRecordId, $destinationRecordIds, $params = false)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

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

630
	public function addRelation($sourceRecordId, $destinationRecordIds, /** @scrutinizer ignore-unused */ $params = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
631
	{
632
		$result = false;
633
		$sourceModuleName = $this->getParentModuleModel()->getName();
634
		$relationModel = $this->getTypeRelationModel();
635
		if (!\is_array($destinationRecordIds)) {
636
			$destinationRecordIds = [$destinationRecordIds];
637
		}
638
		$data = [
639
			'CRMEntity' => $this->getParentModuleModel()->getEntityInstance(),
640
			'sourceModule' => $sourceModuleName,
641
			'sourceRecordId' => $sourceRecordId,
642
			'destinationModule' => $this->getRelationModuleModel()->getName(),
643
			'relationId' => $this->getId(),
644
		];
645
		$eventHandler = (new \App\EventHandler())->setModuleName($this->getRelationModuleModel()->getName());
646
		$eventHandlerBySource = (new \App\EventHandler())->setModuleName($sourceModuleName);
647
		if (!empty($this->getHandlerExceptions())) {
648
			$eventHandler->setExceptions($this->getHandlerExceptions());
649
		}
650
		foreach ($destinationRecordIds as $destinationRecordId) {
651
			$data['destinationRecordId'] = $destinationRecordId;
652
			$eventHandler->setParams($data)->trigger('EntityBeforeLink');
653
			$eventHandlerBySource->setParams($data)->trigger('EntityBeforeLinkForSource');
654
			if ($result = $relationModel->create($sourceRecordId, $destinationRecordId)) {
655
				\CRMEntity::trackLinkedInfo($sourceRecordId);
656
				$eventHandler->trigger('EntityAfterLink');
657
				$eventHandler->trigger('EntityAfterLinkForSource');
658
			}
659
		}
660
		return $result;
661
	}
662
663
	/**
664
	 * Set handler exceptions.
665
	 *
666
	 * @param array $exceptions
667
	 */
668
	public function setHandlerExceptions($exceptions): void
669
	{
670
		$this->handlerExceptions = $exceptions;
671
	}
672
673
	/**
674
	 * get handler exceptions.
675
	 *
676
	 * @return array
677
	 */
678
	public function getHandlerExceptions(): array
679
	{
680
		return $this->handlerExceptions;
681
	}
682
683
	/**
684
	 * Transfer.
685
	 *
686
	 * @param array $recordsToTransfer
687
	 */
688
	public function transfer(array $recordsToTransfer)
689
	{
690
		$relationModel = $this->getTypeRelationModel();
691
		$eventHandler = new \App\EventHandler();
692
		$eventHandler->setModuleName($this->getParentModuleModel()->getName());
693
		$toRecordId = $this->get('parentRecord')->getId();
694
		$params = ['sourceRecordId' => $toRecordId, 'sourceModule' => $eventHandler->getModuleName(), 'destinationModule' => $this->getRelationModuleModel()->getName()];
695
696
		foreach ($recordsToTransfer as $relatedRecordId => $fromRecordId) {
697
			$params['destinationRecordId'] = $relatedRecordId;
698
			$eventHandler->setParams($params);
699
			$eventHandler->trigger('EntityBeforeTransferUnLink');
700
			if ($relationModel->transfer($relatedRecordId, $fromRecordId, $toRecordId)) {
701
				\CRMEntity::trackLinkedInfo([$toRecordId, $fromRecordId]);
702
				$eventHandler->trigger('EntityAfterTransferLink');
703
			}
704
		}
705
	}
706
707
	/**
708
	 * Transfer tree relation.
709
	 *
710
	 * @param array $relationRecords
711
	 */
712
	public function transferTree(array $relationRecords)
713
	{
714
		$recordId = $this->get('parentRecord')->getId();
715
		$dbCommand = \App\Db::getInstance()->createCommand();
716
		foreach ($relationRecords as $tree => $fromId) {
717
			if ($dbCommand->update('u_#__crmentity_rel_tree', ['crmid' => $recordId], ['crmid' => $fromId, 'relmodule' => $this->getRelationModuleModel()->getId(), 'tree' => $tree])->execute()) {
718
				$dbCommand->update('vtiger_crmentity', ['modifiedtime' => date('Y-m-d H:i:s'), 'modifiedby' => \App\User::getCurrentUserId()], ['crmid' => [$fromId, $recordId]])->execute();
719
			}
720
		}
721
	}
722
723
	/**
724
	 * Delete relation.
725
	 *
726
	 * @param int $relId
727
	 */
728 2
	public function transferDelete(int $relId)
729
	{
730 2
		$recordId = $this->get('parentRecord')->getId();
731 2
		$params = ['sourceRecordId' => $recordId,
732
			'sourceModule' => $this->getParentModuleModel()->getName(),
733
			'destinationModule' => $this->getRelationModuleModel()->getName(),
734 2
			'destinationRecordId' => $relId, ];
735 2
		$eventHandler = new \App\EventHandler();
736 2
		$eventHandler->setModuleName($this->getParentModuleModel()->getName());
737 2
		$eventHandler->setParams($params);
738 2
		$eventHandler->trigger('EntityBeforeTransferUnLink');
739 2
		if ($this->getTypeRelationModel()->delete($recordId, $relId)) {
740 1
			$eventHandler->trigger('EntityAfterTransferUnLink');
741
		}
742 2
	}
743 2
744
	/**
745 2
	 * Delete relation.
746 2
	 *
747
	 * @param int $sourceRecordId
748 2
	 * @param int $relatedRecordId
749 2
	 *
750 2
	 * @return bool
751 2
	 */
752
	public function deleteRelation($sourceRecordId, $relatedRecordId)
753 2
	{
754
		$sourceModuleName = $this->getParentModuleModel()->getName();
755
		$destinationModuleName = $this->getRelationModuleModel()->getName();
756 2
		$result = false;
757 2
		if ('ModComments' === $destinationModuleName) {
0 ignored issues
show
introduced by
The condition 'ModComments' === $destinationModuleName is always false.
Loading history...
758 2
			include_once 'modules/ModTracker/ModTracker.php';
759
			ModTracker::unLinkRelation($sourceModuleName, $sourceRecordId, $destinationModuleName, $relatedRecordId);
760 2
			$result = true;
761
		} elseif (!($this->getRelationField() && $this->getRelationField()->isMandatory())) {
762
			$destinationModuleFocus = $this->getRelationModuleModel()->getEntityInstance();
763
			$params = [
764
				'CRMEntity' => $destinationModuleFocus,
765
				'sourceModule' => $sourceModuleName,
766
				'sourceRecordId' => $sourceRecordId,
767
				'destinationModule' => $destinationModuleName,
768
				'destinationRecordId' => $relatedRecordId,
769
				'relatedName' => $this->get('name'),
770
			];
771
			$eventHandler = (new \App\EventHandler())->setModuleName($destinationModuleName)->setParams($params);
772
			$eventHandlerBySource = (new \App\EventHandler())->setModuleName($sourceModuleName)->setParams($params);
773
			$eventHandler->trigger('EntityBeforeUnLink');
774
			$eventHandlerBySource->trigger('EntityBeforeUnLinkForSource');
775
			if ($result = $this->getTypeRelationModel()->delete($sourceRecordId, $relatedRecordId)) {
776
				$destinationModuleFocus->trackUnLinkedInfo($sourceRecordId);
777
				$eventHandler->trigger('EntityAfterUnLink');
778
				$eventHandlerBySource->trigger('EntityAfterUnLinkForSource');
779
			}
780
		}
781
782
		return $result;
783
	}
784
785
	/**
786
	 * Function to add tree type relation.
787
	 *
788
	 * @param int    $crmid
789
	 * @param string $tree
790
	 */
791
	public function addRelationTree($crmid, $tree)
792
	{
793
		App\Db::getInstance()->createCommand()->insert('u_#__crmentity_rel_tree', [
794
			'crmid' => $crmid,
795
			'tree' => $tree,
796
			'module' => $this->getParentModuleModel()->getId(),
797
			'relmodule' => $this->getRelationModuleModel()->getId(),
798
			'rel_created_user' => App\User::getCurrentUserId(),
799
			'rel_created_time' => date('Y-m-d H:i:s'),
800
		])->execute();
801
	}
802
803
	/**
804
	 * Function to delete tree type relation.
805
	 *
806
	 * @param int    $crmid
807
	 * @param string $tree
808
	 */
809
	public function deleteRelationTree($crmid, $tree)
810
	{
811
		App\Db::getInstance()->createCommand()
812
			->delete('u_#__crmentity_rel_tree', ['crmid' => $crmid, 'tree' => $tree, 'module' => $this->getParentModuleModel()->getId(), 'relmodule' => $this->getRelationModuleModel()->getId()])
813
			->execute();
814
	}
815
816
	/**
817
	 * Query tree category relation.
818
	 *
819
	 * @return \App\Db\Query
820
	 */
821
	public function getRelationTreeQuery()
822
	{
823
		$template = [];
824
		foreach ($this->getRelationModuleModel()->getFieldsByType('tree') as $field) {
825
			if ($field->isActiveField()) {
826
				$template[] = $field->getFieldParams();
827
			}
828
		}
829
		return (new \App\Db\Query())
830
			->select(['ttd.*', 'rel.crmid', 'rel.rel_created_time', 'rel.rel_created_user', 'rel.rel_comment'])
831
			->from('vtiger_trees_templates_data ttd')
832
			->innerJoin('u_#__crmentity_rel_tree rel', 'rel.tree = ttd.tree')
833
			->where(['ttd.templateid' => $template, 'rel.crmid' => $this->get('parentRecord')->getId(), 'rel.relmodule' => $this->getRelationModuleModel()->getId()]);
834
	}
835
836
	/**
837
	 * Tree category relation.
838
	 *
839
	 * @return array
840
	 */
841
	public function getRelationTree()
842
	{
843
		return $this->getRelationTreeQuery()->all();
844
	}
845
846
	/**
847
	 * Is the tree type relation available.
848
	 *
849
	 * @return bool
850
	 */
851
	public function isTreeRelation()
852
	{
853
		if (\in_array($this->getRelationModuleModel()->getName(), ['OutsourcedProducts', 'Products', 'Services', 'OSSOutsourcedServices'])) {
854
			foreach ($this->getRelationModuleModel()->getFieldsByType('tree') as $field) {
855
				if ($field->isActiveField()) {
856
					return true;
857
				}
858
			}
859
		}
860
		return false;
861
	}
862
863
	public function isDirectRelation()
864
	{
865
		return self::RELATION_O2M == $this->getRelationType();
866
	}
867
868
	/**
869
	 * Getting all relations.
870
	 *
871
	 * @param \Vtiger_Module_Model $moduleModel
872
	 * @param bool                 $selected
873
	 * @param bool                 $onlyActive
874
	 * @param bool                 $permissions
875
	 * @param string               $key
876
	 *
877
	 * @return \Vtiger_Relation_Model[]
878
	 */
879
	public static function getAllRelations(Vtiger_Module_Model $moduleModel, bool $selected = true, bool $onlyActive = true, bool $permissions = true, string $key = 'relation_id')
880
	{
881
		$relationModels = [];
882
		$relationModelClassName = Vtiger_Loader::getComponentClassName('Model', 'Relation', $moduleModel->get('name'));
883
		$privilegesModel = Users_Privileges_Model::getCurrentUserPrivilegesModel();
884
		foreach (\App\Relation::getByModule($moduleModel->getName(), $onlyActive) as $row) {
0 ignored issues
show
Bug introduced by
$moduleModel->getName() of type boolean is incompatible with the type string expected by parameter $moduleName of App\Relation::getByModule(). ( Ignorable by Annotation )

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

884
		foreach (\App\Relation::getByModule(/** @scrutinizer ignore-type */ $moduleModel->getName(), $onlyActive) as $row) {
Loading history...
885
			if ($selected && 1 === $row['presence']) {
886
				continue;
887
			}
888
			$row['modulename'] = $row['related_modulename'];
889
			$row['moduleid'] = $row['related_tabid'];
890
			// Skip relation where target module does not exits or is no permitted for view.
891
			if ($permissions && !$privilegesModel->hasModuleActionPermission($row['related_modulename'], 'DetailView')) {
892
				continue;
893
			}
894
			$relationModel = new $relationModelClassName();
895
			$relationModel->setData($row)->setParentModuleModel($moduleModel)->set('relatedModuleName', $row['related_modulename']);
896
			$relationModels[$row[$key]] = $relationModel;
897
		}
898
		return $relationModels;
899
	}
900
901
	/**
902
	 * Get autocomplete fields.
903
	 *
904
	 * @param \Vtiger_Record_Model $recordModel
905
	 *
906
	 * @return array
907
	 */
908
	public function getAutoCompleteField($recordModel): array
909
	{
910
		$fields = [];
911
		$fieldsReferenceList = [];
912
		$excludedModules = ['Users'];
913
		$relatedModel = $this->getRelationModuleModel();
914 1
		if ($relationField = $this->getRelationField()) {
915
			$fields[$relationField->getName()] = $recordModel->getId();
916 1
		}
917 1
		$parentModelFields = $this->getParentModuleModel()->getFields();
918 1
		foreach ($parentModelFields as $fieldName => $fieldModel) {
919 1
			if ($fieldModel->isReferenceField()) {
920 1
				$referenceList = $fieldModel->getReferenceList();
921
				foreach ($referenceList as $module) {
922 1
					if (!\in_array($module, $excludedModules) && 'userCreator' !== !$fieldModel->getFieldDataType()) {
923 1
						$fieldsReferenceList[$module] = $fieldModel;
924 1
					}
925
				}
926
			}
927 1
		}
928
		$relatedModelFields = $relatedModel->getFields();
929 1
		foreach ($relatedModelFields as $fieldName => $fieldModel) {
930
			if ($fieldModel->isReferenceField()) {
931
				$referenceList = $fieldModel->getReferenceList();
932
				foreach ($referenceList as $module) {
933
					if (\array_key_exists($module, $fieldsReferenceList) && $module != $recordModel->getModuleName()) {
934
						$parentFieldModel = $fieldsReferenceList[$module];
935
						$relId = $recordModel->get($parentFieldModel->getName());
936 1
						if (!empty($relId) && \App\Record::isExists($relId)) {
937
							$fields[$fieldName] = $relId;
938
						}
939 1
					}
940
				}
941 1
			}
942 1
		}
943 1
944
		return $fields;
945 1
	}
946
947
	public function getRestrictionsPopupField($recordModel)
948
	{
949 1
		$fields = [];
950 1
		$map = [];
951
		$relatedModel = $this->getRelationModuleModel();
952 1
		$relatedModuleName = $relatedModel->getName();
953
		$parentModuleName = $this->getParentModuleModel()->getName();
954
955
		if (\array_key_exists("$relatedModuleName::$parentModuleName", $map)) {
956
			$fieldMap = $map["$relatedModuleName::$parentModuleName"];
957
			$fieldModel = $recordModel->getField($fieldMap[1]);
958
			$value = $fieldModel->getEditViewDisplayValue($recordModel->get($fieldMap[1]), $recordModel);
959
			$fields = ['key' => $fieldMap[0], 'name' => strip_tags($value)];
960
		}
961
		return $fields;
962
	}
963
964
	/**
965
	 * Function to set presence relation.
966
	 *
967
	 * @param int    $relationId
968
	 * @param string $status
969
	 */
970
	public static function updateRelationPresence($relationId, $status)
971
	{
972
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['presence' => !$status ? 1 : 0], ['relation_id' => $relationId])->execute();
973
		\App\Relation::clearCacheById($relationId);
974
	}
975
976
	/**
977
	 * Function to set presence relation.
978
	 *
979
	 * @param int   $relationId
980
	 * @param array $customView
981
	 */
982
	public static function updateRelationCustomView(int $relationId, array $customView): void
983
	{
984
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['custom_view' => implode(',', $customView)], ['relation_id' => $relationId])->execute();
985
		\App\Relation::clearCacheById($relationId);
986
	}
987
988
	/**
989
	 * Removes relation between modules.
990
	 *
991
	 * @param int $relationId
992
	 */
993
	public static function removeRelationById($relationId)
994
	{
995
		if ($relationId) {
996
			$dbCommand = App\Db::getInstance()->createCommand();
997
			$dbCommand->delete('vtiger_relatedlists', ['relation_id' => $relationId])->execute();
998
			$dbCommand->delete('vtiger_relatedlists_fields', ['relation_id' => $relationId])->execute();
999
			App\Db::getInstance('admin')->createCommand()->delete('a_yf_relatedlists_inv_fields', ['relation_id' => $relationId])->execute();
1000
		}
1001
		\App\Relation::clearCacheById($relationId);
1002
	}
1003
1004
	/**
1005
	 * Function to save sequence of relation.
1006
	 *
1007
	 * @param array $modules
1008
	 */
1009
	public static function updateRelationSequence($modules)
1010
	{
1011
		$dbCommand = App\Db::getInstance()->createCommand();
1012
		foreach ($modules as $module) {
1013
			$dbCommand->update('vtiger_relatedlists', ['sequence' => (int) $module['index'] + 1], ['relation_id' => $module['relationId']])->execute();
1014
			\App\Relation::clearCacheById((int) $module['relationId']);
1015
		}
1016
	}
1017
1018
	/**
1019
	 * Update module related fields.
1020
	 *
1021
	 * @param int   $relationId
1022
	 * @param array $fields
1023
	 *
1024
	 * @throws \yii\db\Exception
1025
	 */
1026
	public static function updateModuleRelatedFields(int $relationId, $fields)
1027
	{
1028
		$db = \App\Db::getInstance();
1029
		$transaction = $db->beginTransaction();
1030
		try {
1031
			$db->createCommand()->delete('vtiger_relatedlists_fields', ['relation_id' => $relationId])->execute();
1032
			if ($fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1033
				$addedFields = [];
1034
				foreach ($fields as $key => $field) {
1035
					if (\in_array($field['id'], $addedFields)) {
1036
						continue;
1037
					}
1038
					$db->createCommand()->insert('vtiger_relatedlists_fields', [
1039
						'relation_id' => $relationId,
1040
						'fieldid' => $field['id'],
1041
						'sequence' => $key,
1042
					])->execute();
1043
					$addedFields[] = $field['id'];
1044
				}
1045
			}
1046
			$transaction->commit();
1047
		} catch (\Throwable $e) {
1048
			$transaction->rollBack();
1049
			throw $e;
1050
		}
1051
		\App\Relation::clearCacheById($relationId);
1052
	}
1053
1054
	public static function updateModuleRelatedInventoryFields($relationId, $fields)
1055
	{
1056
		$db = \App\Db::getInstance('admin');
1057
		$db->createCommand()->delete('a_#__relatedlists_inv_fields', ['relation_id' => $relationId])->execute();
1058
		if ($fields) {
1059
			foreach ($fields as $key => $field) {
1060
				$db->createCommand()->insert('a_#__relatedlists_inv_fields', [
1061
					'relation_id' => $relationId,
1062
					'fieldname' => $field,
1063
					'sequence' => $key,
1064
				])->execute();
1065
			}
1066
		}
1067
		\App\Cache::clear();
1068
	}
1069
1070
	public function isActive()
1071
	{
1072
		return 0 == $this->get('presence') ? true : false;
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $this->get('presence') of type mixed|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
1073
	}
1074
1075
	public function getFields($type = false)
1076
	{
1077
		$fields = $this->get('fields');
1078
		if (!$fields) {
1079
			$fields = [];
1080
			$relatedModel = $this->getRelationModuleModel();
1081
			$relatedModelFields = $relatedModel->getFields();
1082
1083
			foreach ($relatedModelFields as $fieldModel) {
1084
				if ($fieldModel->isViewable()) {
1085
					$fields[] = $fieldModel;
1086
				}
1087
			}
1088
			$this->set('fields', $fields);
1089
		}
1090
		if ($type) {
1091
			foreach ($fields as $key => $fieldModel) {
1092
				if ($fieldModel->getFieldDataType() != $type) {
1093
					unset($fields[$key]);
1094
				}
1095
			}
1096
		}
1097
		return $fields;
1098
	}
1099
1100
	/**
1101
	 * Gets relation data fields.
1102
	 *
1103
	 * @return array
1104
	 */
1105
	public function getRelationFields(): array
1106
	{
1107
		return method_exists($this->getTypeRelationModel(), 'getFields') ? $this->getTypeRelationModel()->getFields() : [];
1108
	}
1109
1110
	/**
1111
	 * Set conditions for relation fields.
1112
	 *
1113
	 * @param array $conditions
1114
	 *
1115
	 * @return self
1116
	 */
1117
	public function setRelationConditions(array $conditions): self
1118
	{
1119
		$group = 'and';
1120
		$relFields = $this->getRelationFields();
1121
		foreach ($conditions as $groupInfo) {
1122
			if (empty($groupInfo) || !array_filter($groupInfo)) {
1123
				$group = 'or';
1124
				continue;
1125
			}
1126
			$dataGroup = [$group];
1127
			foreach ($groupInfo as $fieldSearchInfo) {
1128
				[$fieldName, $operator, $fieldValue] = array_pad($fieldSearchInfo, 3, false);
1129
				$field = $relFields[$fieldName] ?? null;
1130
				if (!$field || (($className = '\App\Conditions\QueryFields\\' . ucfirst($field->getFieldDataType()) . 'Field') && !class_exists($className))) {
1131
					continue;
1132
				}
1133
				$queryField = new $className($this->getQueryGenerator(), $field);
1134
				$queryField->setValue($fieldValue);
1135
				$queryField->setOperator($operator);
1136
				$condition = $queryField->getCondition();
1137
				if ($condition) {
1138
					$dataGroup[] = $condition;
1139
				}
1140
			}
1141
			$this->getQueryGenerator()->addNativeCondition($dataGroup);
1142
		}
1143
		return $this;
1144
	}
1145
1146
	public static function getReferenceTableInfo($moduleName, $refModuleName)
1147
	{
1148
		$temp = [$moduleName, $refModuleName];
1149
		sort($temp);
1150
		$tableName = 'u_yf_' . strtolower($temp[0]) . '_' . strtolower($temp[1]);
1151
1152
		if ($temp[0] == $moduleName) {
1153
			$baseColumn = 'relcrmid';
1154
			$relColumn = 'crmid';
1155
		} else {
1156
			$baseColumn = 'crmid';
1157
			$relColumn = 'relcrmid';
1158
		}
1159
		return ['table' => $tableName, 'module' => $temp[0], 'base' => $baseColumn, 'rel' => $relColumn];
1160
	}
1161
1162
	public function updateFavoriteForRecord($action, $data)
1163
	{
1164
		$db = App\Db::getInstance();
1165
		$moduleName = $this->getParentModuleModel()->get('name');
1166
		$result = false;
1167
		if ('add' === $action) {
1168
			$result = $db->createCommand()->insert('u_#__favorites', [
1169
				'data' => date('Y-m-d H:i:s'),
1170
				'crmid' => $data['crmid'],
1171
				'module' => $moduleName,
1172
				'relcrmid' => $data['relcrmid'],
1173
				'relmodule' => $this->getRelationModuleName(),
1174
				'userid' => App\User::getCurrentUserId(),
1175
			])->execute();
1176
		} elseif ('delete' === $action) {
1177
			$result = $db->createCommand()->delete('u_#__favorites', [
1178
				'crmid' => $data['crmid'],
1179
				'module' => $moduleName,
1180
				'relcrmid' => $data['relcrmid'],
1181
				'relmodule' => $this->getRelationModuleName(),
1182
				'userid' => App\User::getCurrentUserId(),
1183
			])->execute();
1184
		}
1185
		return $result;
1186
	}
1187
1188
	public static function updateStateFavorites($relationId, $status)
1189
	{
1190
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['favorites' => $status], ['relation_id' => $relationId])->execute();
1191
		\App\Relation::clearCacheById($relationId);
1192
	}
1193
1194
	/**
1195
	 * Get custom view list.
1196
	 *
1197
	 * @return string[]
1198
	 */
1199
	public function getCustomViewList(): array
1200
	{
1201
		if (isset($this->customViewList)) {
1202
			return $this->customViewList;
1203
		}
1204
		$cv = [];
1205
		$selectedCv = $this->getCustomView();
1206
		if (empty($selectedCv) || \in_array('relation', $selectedCv)) {
1207
			$cv['relation'] = \App\Language::translate('LBL_RECORDS_FROM_RELATION');
1208
			unset($selectedCv[array_search('relation', $selectedCv)]);
1209
		}
1210
		if ($selectedCv) {
1211
			$moduleName = $this->getRelationModuleName();
1212
			$all = CustomView_Record_Model::getAll($moduleName);
1213
			if (\in_array('all', $selectedCv)) {
1214
				unset($selectedCv[array_search('all', $selectedCv)]);
1215
				foreach ($all as $cvId => $cvModel) {
1216
					$cv[$cvId] = \App\Language::translate($cvModel->get('viewname'), $moduleName);
1217
				}
1218
			} elseif (\in_array('private', $selectedCv)) {
1219
				unset($selectedCv[array_search('private', $selectedCv)]);
1220
				foreach ($all as $cvId => $cvModel) {
1221
					if ($cvModel->isMine()) {
1222
						$cv[$cvId] = \App\Language::translate($cvModel->get('viewname'), $moduleName);
1223
					}
1224
				}
1225
			}
1226
			foreach ($selectedCv as $cvId) {
1227
				if (isset($all[$cvId])) {
1228
					$cv[$cvId] = \App\Language::translate($all[$cvId]->get('viewname'), $moduleName);
1229
				}
1230
			}
1231
		}
1232
		return $this->customViewList = $cv;
1233
	}
1234
}
1235