Passed
Push — developer ( 7a4da1...0584e9 )
by Radosław
25:41
created

updateRelationCustomViewOrderBy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

310
			$row = current(\App\Relation::getByModule(/** @scrutinizer ignore-type */ $parentModuleModel->getName(), true, $relatedModuleModel->getName()));
Loading history...
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

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

496
			$relatedModuleInventoryModel = Vtiger_Inventory_Model::getInstance(/** @scrutinizer ignore-type */ $relatedModuleModel->getName());
Loading history...
497
			$relationField = $relatedModuleInventoryModel->getField($this->get('field_name'));
498
		}
499
		$this->set('RelationField', $relationField ?: false);
500
		return $relationField;
501
	}
502
503
	/**
504
	 * Get relation inventory fields.
505
	 *
506
	 * @return Vtiger_Basic_InventoryField[]
507
	 */
508
	public function getRelationInventoryFields()
509
	{
510
		if (!$this->has('RelationInventoryFields')) {
511
			$this->set('RelationInventoryFields', []);
512
			if ($this->getRelationModuleModel()->isInventory()) {
513
				$columns = (new \App\Db\Query())
514
					->select(['fieldname'])
515
					->from('a_#__relatedlists_inv_fields')
516
					->where(['relation_id' => $this->getId()])
517
					->orderBy('sequence')
518
					->column();
519
				$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

519
				$inventoryFields = Vtiger_Inventory_Model::getInstance(/** @scrutinizer ignore-type */ $this->getRelationModuleModel()->getName())->getFields();
Loading history...
520
				$fields = [];
521
				foreach ($columns as &$column) {
522
					if (!empty($inventoryFields[$column]) && $inventoryFields[$column]->isVisible()) {
523
						$fields[$column] = $inventoryFields[$column];
524
					}
525
				}
526
				$this->set('RelationInventoryFields', $fields);
527
			}
528
		}
529
		return $this->get('RelationInventoryFields');
530
	}
531
532
	/**
533
	 * Function which will specify whether the relation is editable.
534
	 *
535
	 * @return bool
536
	 */
537
	public function isEditable(): bool
538
	{
539
		return $this->getRelationModuleModel()->isPermitted('EditView');
540
	}
541
542
	/**
543
	 * Function which will specify whether the relation is deletable.
544
	 *
545
	 * @param \Vtiger_Record_Model|null $recordModel
546
	 * @param int|null                  $recordId
547
	 *
548
	 * @return bool
549
	 */
550
	public function privilegeToDelete(Vtiger_Record_Model $recordModel = null, int $recordId = null): bool
551
	{
552
		$returnVal = $this->getRelationModuleModel()->isPermitted('RemoveRelation');
553
		if ($returnVal && $this->getRelationType() === static::RELATION_O2M && ($fieldModel = $this->getRelationField())) {
554
			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...
555
				$recordModel = \Vtiger_Record_Model::getInstanceById($recordId);
556
			}
557
			$returnVal = !$fieldModel->isMandatory() && $fieldModel->isEditable() && !$fieldModel->isEditableReadOnly() && (!$recordModel || $recordModel->isEditable());
558
		}
559
		if ($returnVal && $this->getRelationType() === static::RELATION_AR && ($fieldModel = $this->getRelationField())) {
0 ignored issues
show
Unused Code introduced by
The assignment to $fieldModel is dead and can be removed.
Loading history...
560
			$returnVal = false;
561
		}
562
		return $returnVal;
563
	}
564
565
	/**
566
	 * Function which will specify whether the tree element is deletable.
567
	 *
568
	 * @return bool
569
	 */
570
	public function privilegeToTreeDelete(): bool
571
	{
572
		return $this->getRelationModuleModel()->isPermitted('RemoveRelation');
573
	}
574
575
	/**
576
	 * Get list url for record.
577
	 *
578
	 * @param Vtiger_Module_Model $parentRecordModel
579
	 *
580
	 * @return string
581
	 */
582
	public function getListUrl(Vtiger_Record_Model $parentRecordModel): string
583
	{
584
		$url = 'index.php?module=' . $this->getParentModuleModel()->get('name') . '&relatedModule=' . $this->get('modulename') .
585
			'&view=Detail&record=' . $parentRecordModel->getId() . '&mode=showRelatedList&relationId=' . $this->getId();
586
		if ('Calendar' == $this->get('modulename')) {
587
			$url .= '&time=current';
588
		}
589
		return $url;
590
	}
591
592
	/**
593
	 * Get create url from parent record.
594
	 *
595
	 * @param bool $fullView
596
	 *
597
	 * @return string
598
	 */
599
	public function getCreateViewUrl(bool $fullView = false)
600
	{
601
		$parentRecord = $this->getParentRecord();
602
		$relatedModuleModel = $this->getRelationModuleModel();
603
		if (!$fullView && $relatedModuleModel->isQuickCreateSupported()) {
604
			$createViewUrl = $relatedModuleModel->getQuickCreateUrl();
605
		} else {
606
			$createViewUrl = $relatedModuleModel->getCreateRecordUrl();
607
		}
608
		$createViewUrl .= '&sourceModule=' . $parentRecord->getModule()->getName() . '&sourceRecord=' . $parentRecord->getId() . '&relationOperation=true&relationId=' . $this->getId();
609
		if ($this->isDirectRelation()) {
610
			$relationField = $this->getRelationField();
611
			$createViewUrl .= '&' . $relationField->getName() . '=' . $parentRecord->getId();
612
		}
613
614
		return $createViewUrl;
615
	}
616
617
	/**
618
	 * Get delete url from parent record.
619
	 *
620
	 * @param int $relatedRecordId
621
	 *
622
	 * @return string
623
	 */
624
	public function getDeleteUrl(int $relatedRecordId)
625
	{
626
		$parentModuleName = $this->getParentModuleModel()->getName();
627
		$relatedModuleName = $this->getRelationModuleModel()->getName();
628
		$recordId = $this->getParentRecord()->getId();
629
630
		return "index.php?module={$parentModuleName}&related_module={$relatedModuleName}&action=RelationAjax&mode=deleteRelation&related_record_list=[{$relatedRecordId}]&src_record={$recordId}&relationId={$this->getId()}";
631
	}
632
633
	/**
634
	 * Add relation.
635
	 *
636
	 * @param int       $sourceRecordId
637
	 * @param int|int[] $destinationRecordIds
638
	 * @param mixed     $params
639
	 */
640
	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

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

900
		foreach (\App\Relation::getByModule(/** @scrutinizer ignore-type */ $moduleModel->getName(), $onlyActive) as $row) {
Loading history...
901
			if ($selected && 1 === $row['presence']) {
902
				continue;
903
			}
904
			$row['modulename'] = $row['related_modulename'];
905
			$row['moduleid'] = $row['related_tabid'];
906
			// Skip relation where target module does not exits or is no permitted for view.
907
			if ($permissions && !$privilegesModel->hasModuleActionPermission($row['related_modulename'], 'DetailView')) {
908
				continue;
909
			}
910
			$relationModel = new $relationModelClassName();
911
			$relationModel->setData($row)->setParentModuleModel($moduleModel)->set('relatedModuleName', $row['related_modulename']);
912
			$relationModels[$row[$key]] = $relationModel;
913
		}
914 1
		return $relationModels;
915
	}
916 1
917 1
	/**
918 1
	 * Get autocomplete fields.
919 1
	 *
920 1
	 * @param \Vtiger_Record_Model $recordModel
921
	 *
922 1
	 * @return array
923 1
	 */
924 1
	public function getAutoCompleteField($recordModel): array
925
	{
926
		$fields = [];
927 1
		$fieldsReferenceList = [];
928
		$excludedModules = ['Users'];
929 1
		$relatedModel = $this->getRelationModuleModel();
930
		if ($relationField = $this->getRelationField()) {
931
			$fields[$relationField->getName()] = $recordModel->getId();
932
		}
933
		$parentModelFields = $this->getParentModuleModel()->getFields();
934
		foreach ($parentModelFields as $fieldName => $fieldModel) {
935
			if ($fieldModel->isReferenceField()) {
936 1
				$referenceList = $fieldModel->getReferenceList();
937
				foreach ($referenceList as $module) {
938
					if (!\in_array($module, $excludedModules) && 'userCreator' !== !$fieldModel->getFieldDataType()) {
939 1
						$fieldsReferenceList[$module] = $fieldModel;
940
					}
941 1
				}
942 1
			}
943 1
		}
944
		$relatedModelFields = $relatedModel->getFields();
945 1
		foreach ($relatedModelFields as $fieldName => $fieldModel) {
946
			if ($fieldModel->isReferenceField()) {
947
				$referenceList = $fieldModel->getReferenceList();
948
				foreach ($referenceList as $module) {
949 1
					if (\array_key_exists($module, $fieldsReferenceList) && $module != $recordModel->getModuleName()) {
950 1
						$parentFieldModel = $fieldsReferenceList[$module];
951
						$relId = $recordModel->get($parentFieldModel->getName());
952 1
						if (!empty($relId) && \App\Record::isExists($relId)) {
953
							$fields[$fieldName] = $relId;
954
						}
955
					}
956
				}
957
			}
958
		}
959
960
		return $fields;
961
	}
962
963
	public function getRestrictionsPopupField($recordModel)
964
	{
965
		$fields = [];
966
		$map = [];
967
		$relatedModel = $this->getRelationModuleModel();
968
		$relatedModuleName = $relatedModel->getName();
969
		$parentModuleName = $this->getParentModuleModel()->getName();
970
971
		if (\array_key_exists("$relatedModuleName::$parentModuleName", $map)) {
972
			$fieldMap = $map["$relatedModuleName::$parentModuleName"];
973
			$fieldModel = $recordModel->getField($fieldMap[1]);
974
			$value = $fieldModel->getEditViewDisplayValue($recordModel->get($fieldMap[1]), $recordModel);
975
			$fields = ['key' => $fieldMap[0], 'name' => strip_tags($value)];
976
		}
977
		return $fields;
978
	}
979
980
	/**
981
	 * Function to set presence relation.
982
	 *
983
	 * @param int    $relationId
984
	 * @param string $status
985
	 */
986
	public static function updateRelationPresence($relationId, $status)
987
	{
988
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['presence' => !$status ? 1 : 0], ['relation_id' => $relationId])->execute();
989
		\App\Relation::clearCacheById($relationId);
990
	}
991
992
	/**
993
	 * Function to set presence relation.
994
	 *
995
	 * @param int   $relationId
996
	 * @param array $customView
997
	 */
998
	public static function updateRelationCustomView(int $relationId, array $customView): void
999
	{
1000
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['custom_view' => implode(',', $customView)], ['relation_id' => $relationId])->execute();
1001
		\App\Relation::clearCacheById($relationId);
1002
	}
1003
1004
	/**
1005
	 * Function to set custom view orderby .
1006
	 *
1007
	 * @param int  $relationId
1008
	 * @param bool $customViewOrderBy
1009
	 */
1010
	public static function updateRelationCustomViewOrderBy(int $relationId, bool $customViewOrderBy): void
1011
	{
1012
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['custom_view_orderby' => $customViewOrderBy], ['relation_id' => $relationId])->execute();
1013
		\App\Relation::clearCacheById($relationId);
1014
	}
1015
1016
	/**
1017
	 * Removes relation between modules.
1018
	 *
1019
	 * @param int $relationId
1020
	 */
1021
	public static function removeRelationById($relationId)
1022
	{
1023
		if ($relationId) {
1024
			$dbCommand = App\Db::getInstance()->createCommand();
1025
			$dbCommand->delete('vtiger_relatedlists', ['relation_id' => $relationId])->execute();
1026
			$dbCommand->delete('vtiger_relatedlists_fields', ['relation_id' => $relationId])->execute();
1027
			App\Db::getInstance('admin')->createCommand()->delete('a_yf_relatedlists_inv_fields', ['relation_id' => $relationId])->execute();
1028
			$widgets = (new \App\Db\Query())->select(['id', 'tabid'])->from('vtiger_widgets')->where(['and', ['type' => 'RelatedModule'], ['like', 'data', "\"relation_id\":{$relationId},"]])->createCommand()->queryAllByGroup();
1029
			foreach ($widgets as $widgetId => $tabId) {
1030
				$dbCommand->delete('vtiger_widgets', ['id' => $widgetId])->execute();
1031
				\App\Cache::delete('ModuleWidgets', $tabId);
1032
			}
1033
		}
1034
		\App\Relation::clearCacheById($relationId);
1035
	}
1036
1037
	/**
1038
	 * Function to save sequence of relation.
1039
	 *
1040
	 * @param array $modules
1041
	 */
1042
	public static function updateRelationSequence($modules)
1043
	{
1044
		$dbCommand = App\Db::getInstance()->createCommand();
1045
		foreach ($modules as $module) {
1046
			$dbCommand->update('vtiger_relatedlists', ['sequence' => (int) $module['index'] + 1], ['relation_id' => $module['relationId']])->execute();
1047
			\App\Relation::clearCacheById((int) $module['relationId']);
1048
		}
1049
	}
1050
1051
	/**
1052
	 * Update module related fields.
1053
	 *
1054
	 * @param int   $relationId
1055
	 * @param array $fields
1056
	 *
1057
	 * @throws \yii\db\Exception
1058
	 */
1059
	public static function updateModuleRelatedFields(int $relationId, $fields)
1060
	{
1061
		$db = \App\Db::getInstance();
1062
		$transaction = $db->beginTransaction();
1063
		try {
1064
			$db->createCommand()->delete('vtiger_relatedlists_fields', ['relation_id' => $relationId])->execute();
1065
			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...
1066
				$addedFields = [];
1067
				foreach ($fields as $key => $field) {
1068
					if (\in_array($field['id'], $addedFields)) {
1069
						continue;
1070
					}
1071
					$db->createCommand()->insert('vtiger_relatedlists_fields', [
1072
						'relation_id' => $relationId,
1073
						'fieldid' => $field['id'],
1074
						'sequence' => $key,
1075
					])->execute();
1076
					$addedFields[] = $field['id'];
1077
				}
1078
			}
1079
			$transaction->commit();
1080
		} catch (\Throwable $e) {
1081
			$transaction->rollBack();
1082
			throw $e;
1083
		}
1084
		\App\Relation::clearCacheById($relationId);
1085
	}
1086
1087
	public static function updateModuleRelatedInventoryFields($relationId, $fields)
1088
	{
1089
		$db = \App\Db::getInstance('admin');
1090
		$db->createCommand()->delete('a_#__relatedlists_inv_fields', ['relation_id' => $relationId])->execute();
1091
		if ($fields) {
1092
			foreach ($fields as $key => $field) {
1093
				$db->createCommand()->insert('a_#__relatedlists_inv_fields', [
1094
					'relation_id' => $relationId,
1095
					'fieldname' => $field,
1096
					'sequence' => $key,
1097
				])->execute();
1098
			}
1099
		}
1100
		\App\Cache::clear();
1101
	}
1102
1103
	public function isActive()
1104
	{
1105
		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...
1106
	}
1107
1108
	public function getFields($type = false)
1109
	{
1110
		$fields = $this->get('fields');
1111
		if (!$fields) {
1112
			$fields = [];
1113
			$relatedModel = $this->getRelationModuleModel();
1114
			$relatedModelFields = $relatedModel->getFields();
1115
1116
			foreach ($relatedModelFields as $fieldModel) {
1117
				if ($fieldModel->isViewable()) {
1118
					$fields[] = $fieldModel;
1119
				}
1120
			}
1121
			$this->set('fields', $fields);
1122
		}
1123
		if ($type) {
1124
			foreach ($fields as $key => $fieldModel) {
1125
				if ($fieldModel->getFieldDataType() != $type) {
1126
					unset($fields[$key]);
1127
				}
1128
			}
1129
		}
1130
		return $fields;
1131
	}
1132
1133
	/**
1134
	 * Gets relation data fields.
1135
	 *
1136
	 * @return array
1137
	 */
1138
	public function getRelationFields(): array
1139
	{
1140
		return method_exists($this->getTypeRelationModel(), 'getFields') ? $this->getTypeRelationModel()->getFields() : [];
1141
	}
1142
1143
	/**
1144
	 * Set conditions for relation fields.
1145
	 *
1146
	 * @param array $conditions
1147
	 *
1148
	 * @return self
1149
	 */
1150
	public function setRelationConditions(array $conditions): self
1151
	{
1152
		$group = 'and';
1153
		$relFields = $this->getRelationFields();
1154
		foreach ($conditions as $groupInfo) {
1155
			if (empty($groupInfo) || !array_filter($groupInfo)) {
1156
				$group = 'or';
1157
				continue;
1158
			}
1159
			$dataGroup = [$group];
1160
			foreach ($groupInfo as $fieldSearchInfo) {
1161
				[$fieldName, $operator, $fieldValue] = array_pad($fieldSearchInfo, 3, false);
1162
				$field = $relFields[$fieldName] ?? null;
1163
				if (!$field || (($className = '\App\Conditions\QueryFields\\' . ucfirst($field->getFieldDataType()) . 'Field') && !class_exists($className))) {
1164
					continue;
1165
				}
1166
				$queryField = new $className($this->getQueryGenerator(), $field);
1167
				$queryField->setValue($fieldValue);
1168
				$queryField->setOperator($operator);
1169
				$condition = $queryField->getCondition();
1170
				if ($condition) {
1171
					$dataGroup[] = $condition;
1172
				}
1173
			}
1174
			$this->getQueryGenerator()->addNativeCondition($dataGroup);
1175
		}
1176
		return $this;
1177
	}
1178
1179
	public static function getReferenceTableInfo($moduleName, $refModuleName)
1180
	{
1181
		$temp = [$moduleName, $refModuleName];
1182
		sort($temp);
1183
		$tableName = 'u_yf_' . strtolower($temp[0]) . '_' . strtolower($temp[1]);
1184
1185
		if ($temp[0] == $moduleName) {
1186
			$baseColumn = 'relcrmid';
1187
			$relColumn = 'crmid';
1188
		} else {
1189
			$baseColumn = 'crmid';
1190
			$relColumn = 'relcrmid';
1191
		}
1192
		return ['table' => $tableName, 'module' => $temp[0], 'base' => $baseColumn, 'rel' => $relColumn];
1193
	}
1194
1195
	public function updateFavoriteForRecord($action, $data)
1196
	{
1197
		$db = App\Db::getInstance();
1198
		$moduleName = $this->getParentModuleModel()->get('name');
1199
		$result = false;
1200
		if ('add' === $action) {
1201
			$result = $db->createCommand()->insert('u_#__favorites', [
1202
				'data' => date('Y-m-d H:i:s'),
1203
				'crmid' => $data['crmid'],
1204
				'module' => $moduleName,
1205
				'relcrmid' => $data['relcrmid'],
1206
				'relmodule' => $this->getRelationModuleName(),
1207
				'userid' => App\User::getCurrentUserId(),
1208
			])->execute();
1209
		} elseif ('delete' === $action) {
1210
			$result = $db->createCommand()->delete('u_#__favorites', [
1211
				'crmid' => $data['crmid'],
1212
				'module' => $moduleName,
1213
				'relcrmid' => $data['relcrmid'],
1214
				'relmodule' => $this->getRelationModuleName(),
1215
				'userid' => App\User::getCurrentUserId(),
1216
			])->execute();
1217
		}
1218
		return $result;
1219
	}
1220
1221
	public static function updateStateFavorites($relationId, $status)
1222
	{
1223
		\App\Db::getInstance()->createCommand()->update('vtiger_relatedlists', ['favorites' => $status], ['relation_id' => $relationId])->execute();
1224
		\App\Relation::clearCacheById($relationId);
1225
	}
1226
1227
	/**
1228
	 * Get custom view list.
1229
	 *
1230
	 * @return string[]
1231
	 */
1232
	public function getCustomViewList(): array
1233
	{
1234
		if (isset($this->customViewList)) {
1235
			return $this->customViewList;
1236
		}
1237
		$cv = [];
1238
		$selectedCv = $this->getCustomView();
1239
		if (empty($selectedCv) || \in_array('relation', $selectedCv)) {
1240
			$cv['relation'] = \App\Language::translate('LBL_RECORDS_FROM_RELATION');
1241
			unset($selectedCv[array_search('relation', $selectedCv)]);
1242
		}
1243
		if ($selectedCv) {
1244
			$moduleName = $this->getRelationModuleName();
1245
			$all = CustomView_Record_Model::getAll($moduleName);
1246
			if (\in_array('all', $selectedCv)) {
1247
				unset($selectedCv[array_search('all', $selectedCv)]);
1248
				foreach ($all as $cvId => $cvModel) {
1249
					$cv[$cvId] = \App\Language::translate($cvModel->get('viewname'), $moduleName);
1250
				}
1251
			} elseif (\in_array('private', $selectedCv)) {
1252
				unset($selectedCv[array_search('private', $selectedCv)]);
1253
				foreach ($all as $cvId => $cvModel) {
1254
					if ($cvModel->isMine()) {
1255
						$cv[$cvId] = \App\Language::translate($cvModel->get('viewname'), $moduleName);
1256
					}
1257
				}
1258
			}
1259
			foreach ($selectedCv as $cvId) {
1260
				if (isset($all[$cvId])) {
1261
					$cv[$cvId] = \App\Language::translate($all[$cvId]->get('viewname'), $moduleName);
1262
				}
1263
			}
1264
		}
1265
		return $this->customViewList = $cv;
1266
	}
1267
1268
	/**
1269
	 * Get sort orderby for custom view.
1270
	 *
1271
	 * @param int|string $cvId
1272
	 *
1273
	 * @return array
1274
	 */
1275
	public function getCustomViewOrderBy($cvId): array
1276
	{
1277
		$orderBy = [];
1278
		if ($cvId && is_numeric($cvId) && $this->get('custom_view_orderby') && ($customViewRecordModel = CustomView_Record_Model::getInstanceById($cvId))) {
1279
			$orderBy = $customViewRecordModel->getSortOrderBy();
1280
		}
1281
		return $orderBy;
1282
	}
1283
}
1284