Vtiger_Record_Model::getValueByFieldModel()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 3
nc 3
nop 1
crap 12
1
<?php
2
3
/* +***********************************************************************************
4
* The contents of this file are subject to the vtiger CRM Public License Version 1.0
5
* ("License"); You may not use this file except in compliance with the License
6
* The Original Code is:  vtiger CRM Open Source
7
* The Initial Developer of the Original Code is vtiger.
8
* Portions created by vtiger are Copyright (C) vtiger.
9
* All Rights Reserved.
10
* Contributor(s): YetiForce S.A.
11
* *********************************************************************************** */
12
13
/**
14
 * Vtiger Entity Record Model Class.
15
 */
16
class Vtiger_Record_Model extends \App\Base
17
{
18
	/**
19
	 * @var string Record label
20
	 */
21
	public $label;
22
	public $isNew = true;
23
	public $ext = [];
24
	/**
25
	 * @var Vtiger_Module_Model Module model
26
	 */
27
	protected $module;
28
	/**
29
	 * @var array Inventory data
30
	 */
31
	protected $inventoryData;
32
	/**
33
	 * @var array Record changes
34 5821
	 */
35
	protected $changes = [];
36 5821
	/**
37
	 * @var array Record inventory changes
38
	 */
39
	protected $changesInventory = [];
40
	/**
41
	 * @var array Data for save
42
	 */
43
	protected $dataForSave = [];
44 30
	/**
45
	 * @var array Event handler exceptions
46 30
	 */
47
	protected $handlerExceptions = [];
48
	protected $handler;
49
	protected $privileges = [];
50
	public $summaryRowCount = 4;
51
52
	/**
53
	 * Function to get the id of the record.
54 5794
	 *
55
	 * @return int - Record Id
56 5794
	 */
57
	public function getId()
58
	{
59
		return $this->get('id');
60
	}
61
62
	/**
63
	 * Function to set the id of the record.
64
	 *
65 5811
	 * @param int $value - id value
66
	 */
67 5811
	public function setId($value)
68 5786
	{
69
		return $this->set('id', (int) $value);
70 5811
	}
71 5811
72
	/**
73
	 * Is new record.
74
	 *
75
	 * @return bool
76
	 */
77
	public function isNew()
78
	{
79
		return $this->isNew;
80
	}
81
82 1
	/**
83
	 * Function to set the value for a given key.
84 1
	 *
85
	 * @param $key
86
	 * @param $value
87 1
	 */
88 1
	public function set($key, $value)
89
	{
90 1
		if (!$this->isNew && !\in_array($key, ['mode', 'id', 'newRecord', 'modifiedtime', 'modifiedby', 'createdtime']) && (\array_key_exists($key, $this->value) && $this->value[$key] != $value)) {
91
			$this->changes[$key] = $this->get($key);
92
		}
93
		$this->value[$key] = $value;
94
		return $this;
95
	}
96
97
	/**
98 1
	 * Function to set the value for a given key and user farmat.
99
	 *
100 1
	 * @param string $fieldName
101 1
	 * @param mixed  $value
102
	 *
103
	 * @return $this
104
	 */
105
	public function setFromUserValue($fieldName, $value)
106
	{
107
		if ('' === $value) {
108 3
			return $this;
109
		}
110 3
		$fieldModel = $this->getModule()->getFieldByName($fieldName);
111 3
		$this->set($fieldName, $fieldModel->getUITypeModel()->getDBValue($value, $this));
112 3
113
		return $this;
114
	}
115
116
	/**
117
	 * Set custom data for save.
118
	 *
119
	 * @param array $data
120
	 *
121
	 * @return $this
122
	 */
123
	public function setDataForSave(array $data): self
124 5794
	{
125
		$db = \App\Db::getInstance();
126 5794
		foreach ($data as $tableName => $tableData) {
127
			$tableName = $db->quoteSql($tableName);
128
			$this->dataForSave[$tableName] = isset($this->dataForSave[$tableName]) ? array_merge($this->dataForSave[$tableName], $tableData) : $tableData;
129
		}
130
		return $this;
131
	}
132
133
	/**
134
	 * Gets custom data for save.
135
	 *
136 2
	 * @param array
137
	 */
138 2
	public function getDataForSave()
139
	{
140
		return $this->dataForSave;
141
	}
142
143
	/**
144
	 * Function to get the Name of the record.
145
	 *
146 19
	 * @return string - Entity Name of the record
147
	 */
148 19
	public function getName(): string
149 19
	{
150
		if (!isset($this->label)) {
151
			$this->label = $this->getDisplayName();
152 19
		}
153
		return $this->label;
154
	}
155
156
	/**
157
	 * Get pervious value by field.
158
	 *
159
	 * @param string $fieldName
160 2
	 *
161
	 * @return mixed
162 2
	 */
163 2
	public function getPreviousValue(string $fieldName = '')
164
	{
165
		return $fieldName ? ($this->changes[$fieldName] ?? false) : $this->changes;
166
	}
167
168
	/**
169
	 * Revert previous value.
170
	 *
171
	 * @param string $fieldName
172
	 *
173
	 * @return void
174
	 */
175
	public function revertPreviousValue(string $fieldName): void
176
	{
177
		if (isset($this->changes[$fieldName])) {
178
			$this->value[$fieldName] = $this->changes[$fieldName];
179
		}
180
	}
181
182
	/**
183
	 * Gets previous values by inventory.
184
	 *
185
	 * @param int|string|null $key
186 5792
	 *
187
	 * @return array|bool
188 5792
	 */
189
	public function getPreviousInventoryItems($key = null)
190
	{
191
		return null !== $key ? ($this->changesInventory[$key] ?? false) : $this->changesInventory;
192
	}
193
194
	/**
195
	 * Gets previous values.
196
	 *
197
	 * @return array
198 2
	 */
199
	public function getChanges()
200 2
	{
201
		$changes = $this->getPreviousValue();
202 2
		if ($this->getModule()->isInventory() && ($prevInv = $this->getPreviousInventoryItems())) {
203
			$changes['inventory'] = $prevInv;
204
		}
205
		return $changes;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $changes could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
206
	}
207
208
	public function getSearchName()
209
	{
210
		$displayName = $this->get('searchlabel');
211
212 31
		return \App\Purifier::encodeHtml(App\Purifier::decodeHtml($displayName));
213
	}
214 31
215 31
	public function isWatchingRecord()
216
	{
217
		if (!isset($this->isWatchingRecord)) {
218
			$watchdog = Vtiger_Watchdog_Model::getInstanceById($this->getId(), $this->getModuleName());
219
			$this->isWatchingRecord = (bool) $watchdog->isWatchingRecord();
0 ignored issues
show
Bug Best Practice introduced by
The property isWatchingRecord does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
220
		}
221
		return $this->isWatchingRecord;
222
	}
223 14
224
	/**
225 14
	 * Function to get the Module to which the record belongs.
226
	 *
227
	 * @return Vtiger_Module_Model
228 14
	 */
229
	public function getModule(): Vtiger_Module_Model
230
	{
231
		return $this->module;
232
	}
233
234
	/**
235
	 * Function to set the Module to which the record belongs.
236
	 *
237
	 * @param string $moduleName
238 30
	 *
239
	 * @return Vtiger_Record_Model Record Model instance
240 30
	 */
241 30
	public function setModule(string $moduleName): self
242
	{
243
		$this->module = Vtiger_Module_Model::getInstance($moduleName);
244
		return $this;
245
	}
246
247
	/**
248
	 * Function to set the Module to which the record belongs from the Module model instance.
249
	 *
250
	 * @param Vtiger_Module_Model $module
251
	 *
252
	 * @return Vtiger_Record_Model Record Model instance
253
	 */
254
	public function setModuleFromInstance(Vtiger_Module_Model $module): self
255
	{
256
		$this->module = $module;
257
		return $this;
258
	}
259
260
	/**
261 2
	 * Function to get the entity instance of the recrod.
262
	 *
263 2
	 * @return CRMEntity object
264
	 */
265 2
	public function getEntity()
266
	{
267
		if (empty($this->entity)) {
268
			$this->entity = CRMEntity::getInstance($this->getModuleName());
0 ignored issues
show
Bug Best Practice introduced by
The property entity does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
269
		}
270
		return $this->entity;
271
	}
272
273
	/**
274
	 * Function to set the entity instance of the record.
275
	 *
276
	 * @param CRMEntity $entity
277
	 *
278
	 * @return Vtiger_Record_Model instance
279
	 */
280
	public function setEntity($entity)
281
	{
282
		$this->entity = $entity;
0 ignored issues
show
Bug Best Practice introduced by
The property entity does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
283
		return $this;
284
	}
285
286
	/**
287
	 * Function to get raw data value by field.
288
	 *
289
	 * @param string $fieldName
290
	 *
291
	 * @return mixed
292
	 */
293
	public function getRawValue(string $fieldName)
294
	{
295
		$value = $this->get($fieldName);
296
		if ($fieldName && $fieldModel = $this->getField($fieldName)) {
297
			$value = $fieldModel->getUITypeModel()->getRawValue($value);
298
		}
299
		return $value;
300
	}
301
302
	/**
303
	 * Get record number.
304
	 *
305
	 * @return string
306
	 */
307
	public function getRecordNumber(): string
308
	{
309
		return $this->get($this->getModule()->getSequenceNumberFieldName()) ?? '';
310
	}
311
312
	/**
313
	 * Function to get the Detail View url for the record.
314
	 *
315
	 * @return string - Record Detail View Url
316
	 */
317
	public function getDetailViewUrl()
318
	{
319
		$menuUrl = '';
320
		if (!empty($_REQUEST['parent']) && 'Settings' !== $_REQUEST['parent']) {
321
			$menuUrl .= '&parent=' . \App\Request::_getInteger('parent');
0 ignored issues
show
Bug introduced by
The method _getInteger() does not exist on App\Request. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

321
			$menuUrl .= '&parent=' . \App\Request::/** @scrutinizer ignore-call */ _getInteger('parent');
Loading history...
322
		}
323
		if (isset($_REQUEST['mid'])) {
324
			$menuUrl .= '&mid=' . \App\Request::_getInteger('mid');
325
		}
326
		return "index.php?module={$this->getModuleName()}&view={$this->getModule()->getDetailViewName()}&record={$this->getId()}{$menuUrl}";
327
	}
328
329
	/**
330
	 * Function to get the complete Detail View url for the record.
331
	 *
332
	 * @return string - Record Detail View Url
333
	 */
334
	public function getFullDetailViewUrl()
335
	{
336
		return $this->getDetailViewUrl() . '&mode=showDetailViewByMode&requestMode=full';
337
	}
338
339
	/**
340
	 * Function to get the Edit View url for the record.
341 5801
	 *
342
	 * @return string - Record Edit View Url
343 5801
	 */
344
	public function getEditViewUrl()
345
	{
346
		$menuUrl = '';
347
		if (isset($_REQUEST['parent'])) {
348
			$menuUrl .= '&parent=' . \App\Request::_getInteger('parent');
349
		}
350
		if (isset($_REQUEST['mid'])) {
351 3
			$menuUrl .= '&mid=' . \App\Request::_getInteger('mid');
352
		}
353 3
		return "index.php?module={$this->getModuleName()}&view={$this->getModule()->getEditViewName()}{$menuUrl}" . ($this->getId() ? '&record=' . $this->getId() : '');
354
	}
355
356
	/**
357
	 * Function to get the Update View url for the record.
358
	 *
359
	 * @return string - Record Upadte view Url
360
	 */
361
	public function getUpdatesUrl()
362
	{
363
		return $this->getDetailViewUrl() . '&mode=showRecentActivities&page=1&tab_label=LBL_UPDATES';
364
	}
365
366 5761
	/**
367
	 * Timeline view URL.
368 5761
	 *
369 5761
	 * @return string
370
	 */
371 5761
	public function getTimeLineUrl()
372 5761
	{
373 5761
		return 'index.php?module=' . $this->getModuleName() . '&view=TimeLineModal&record=' . $this->getId();
374
	}
375
376
	/**
377
	 * Function to get the Delete Action url for the record.
378
	 *
379
	 * @return string - Record Delete Action Url
380
	 */
381
	public function getDeleteUrl()
382
	{
383
		return 'index.php?module=' . $this->getModuleName() . '&action=' . $this->getModule()->getDeleteActionName() . '&record=' . $this->getId();
384
	}
385
386
	/**
387
	 * Function to get the name of the module to which the record belongs.
388
	 *
389
	 * @return string - Record Module Name
390
	 */
391
	public function getModuleName(): string
392
	{
393
		return $this->getModule()->get('name');
394
	}
395
396
	/**
397
	 * Function to get the Display Name for the record.
398
	 *
399
	 * @return string - Entity Display Name for the record
400
	 */
401
	public function getDisplayName()
402
	{
403
		return \App\Record::getLabel($this->getId());
0 ignored issues
show
Bug Best Practice introduced by
The expression return App\Record::getLabel($this->getId()) also could return the type array which is incompatible with the documented return type string.
Loading history...
404
	}
405
406
	/**
407
	 * Function to retieve display value for a field.
408
	 *
409
	 * @param string   $fieldName Field name for which values need to get
410
	 * @param bool|int $record    Record Id
411
	 * @param bool     $rawText
412
	 * @param bool|int $length    Length of the text
413
	 *
414
	 * @return bool|string
415
	 */
416
	public function getDisplayValue($fieldName, $record = false, $rawText = false, $length = false)
417
	{
418
		if (empty($record)) {
419
			$record = $this->getId();
420
		}
421
		$result = false;
422
		$fieldModel = $this->getModule()->getFieldByName($fieldName);
423
		if ($fieldModel) {
424 28
			$result = $fieldModel->getDisplayValue($this->get($fieldName), $record, $this, $rawText, $length);
425
		}
426 28
		return $result;
427
	}
428
429
	/**
430
	 * Function to get the display value in RelatedListView.
431
	 *
432
	 * @param string $fieldName
433
	 *
434
	 * @return string
435
	 */
436
	public function getRelatedListViewDisplayValue($fieldName)
437
	{
438
		$fieldModel = $this->getModule()->getFieldByName($fieldName);
439
		return $fieldModel->getUITypeModel()->getRelatedListViewDisplayValue($this->get($fieldName), $this->getId(), $this);
440
	}
441
442
	/**
443
	 * Function to get the display value in ListView.
444
	 *
445
	 * @param string|Vtiger_Field_Model $field
446
	 * @param bool                      $rawText
447
	 *
448 25
	 * @throws \App\Exceptions\AppException
449
	 *
450 25
	 * @return mixed
451 25
	 */
452 25
	public function getListViewDisplayValue($field, $rawText = false)
453 25
	{
454 25
		if ($field instanceof Vtiger_Field_Model) {
455 25
			if (!empty($field->get('source_field_name')) && isset($this->ext[$field->get('source_field_name')][$field->getModuleName()])) {
456 1
				return $this->ext[$field->get('source_field_name')][$field->getModuleName()]->getListViewDisplayValue($field, $rawText);
457
			}
458 25
		} else {
459 25
			$field = $this->getModule()->getFieldByName($field);
460 25
		}
461
		return $field->getUITypeModel()->getListViewDisplayValue($this->get($field->getName()), $this->getId(), $this, $rawText);
462 25
	}
463 1
464
	/**
465 25
	 * Function to get the display value in Tiles.
466
	 *
467
	 * @param string|Vtiger_Field_Model $field
468 25
	 * @param bool                      $rawText
469
	 *
470 25
	 * @throws \App\Exceptions\AppException
471 25
	 *
472 25
	 * @return string
473
	 */
474
	public function getTilesDisplayValue($field, $rawText = false)
475
	{
476 25
		if ($field instanceof Vtiger_Field_Model) {
477
			if (!empty($field->get('source_field_name')) && isset($this->ext[$field->get('source_field_name')][$field->getModuleName()])) {
478
				return $this->ext[$field->get('source_field_name')][$field->getModuleName()]->getTilesDisplayValue($field, $rawText);
479
			}
480
		} else {
481 25
			$field = $this->getModule()->getFieldByName($field);
482 25
		}
483 13
		return $field->getUITypeModel()->getTilesDisplayValue($this->get($field->getName()), $this->getId(), $this, $rawText);
484 13
	}
485
486 25
	/**
487 25
	 * Function returns the Vtiger_Field_Model.
488 25
	 *
489 25
	 * @param string $fieldName - field name
490
	 *
491
	 * @return Vtiger_Field_Model|false
492
	 */
493
	public function getField($fieldName)
494 25
	{
495
		return $this->getModule()->getFieldByName($fieldName);
496 25
	}
497 25
498 25
	/**
499 25
	 * Function returns all the field values in user format.
500 13
	 *
501 13
	 * @return array
502 13
	 */
503
	public function getDisplayableValues(): array
504 13
	{
505
		$displayableValues = [];
506
		$data = $this->getData();
507 19
		foreach ($data as $fieldName => $value) {
508
			$fieldValue = $this->getDisplayValue($fieldName);
509
			$displayableValues[$fieldName] = ($fieldValue) ?: $value;
510 25
		}
511 2
		return $displayableValues;
512
	}
513 25
514
	/**
515
	 * Gets Event Handler.
516
	 *
517
	 * @return \App\EventHandler
518
	 */
519
	public function getEventHandler(): App\EventHandler
520 25
	{
521
		if (!$this->handler) {
522 25
			$this->handler = (new \App\EventHandler())->setRecordModel($this)->setModuleName($this->getModuleName());
523 25
		}
524 25
		return $this->handler;
525
	}
526 25
527 19
	/**
528
	 * Function to save the current Record Model.
529 13
	 */
530 13
	public function save()
531 13
	{
532 13
		$eventHandler = $this->getEventHandler();
533
		if ($this->getHandlerExceptions()) {
534 13
			$eventHandler->setExceptions($this->getHandlerExceptions());
535 13
		}
536 13
		$eventHandler->trigger('EntityBeforeSave');
537
		$db = \App\Db::getInstance();
538
		$transaction = $db->beginTransaction();
539
		try {
540 25
			if (!$this->isNew() && !$this->isMandatorySave() && empty($this->getPreviousValue())) {
541 1
				App\Log::info('ERR_NO_DATA');
542
			} else {
543 25
				if (method_exists($this, 'validate')) {
544 25
					$this->validate();
545 25
				}
546 25
				$this->saveToDb();
547 25
			}
548 25
			Users_Privileges_Model::setSharedOwner($this->get('shownerid'), $this->getId());
549 25
			\App\Record::updateLabelOnSave($this);
550 14
			$this->addRelations();
551 14
			$transaction->commit();
552 6
		} catch (\Exception $e) {
553
			$transaction->rollBack();
554 14
			throw $e;
555
		}
556
		$eventHandler->trigger('EntityAfterSave');
557 25
		if ($this->isNew()) {
558
			\App\Cache::staticSave('RecordModel', $this->getId() . ':' . $this->getModuleName(), $this);
559
			$this->isNew = false;
560 25
		}
561
		\App\Cache::staticDelete('UnlockFields', $this->getId());
562
		\App\PrivilegeUpdater::updateOnRecordSave($this);
563
	}
564
565
	/**
566
	 * Save data to the database.
567
	 */
568 25
	public function saveToDb()
569
	{
570 25
		$entityInstance = $this->getModule()->getEntityInstance();
571 25
		$db = \App\Db::getInstance();
572 25
		foreach ($this->getValuesForSave() as $tableName => $tableData) {
573 13
			if ($this->isNew()) {
574 13
				if ('vtiger_crmentity' === $tableName) {
575 13
					$db->createCommand()->insert($tableName, $tableData)->execute();
576 13
					$this->setId((int) $db->getLastInsertID('vtiger_crmentity_crmid_seq'));
577 13
				} else {
578
					$db->createCommand()->insert($tableName, [$entityInstance->tab_name_index[$tableName] => $this->getId()] + $tableData)->execute();
579 25
				}
580 25
			} else {
581 25
				$db->createCommand()->update($tableName, $tableData, [$entityInstance->tab_name_index[$tableName] => $this->getId()])->execute();
582 25
			}
583 25
		}
584
		if ($this->getModule()->isInventory()) {
585
			$this->saveInventoryData();
586
		}
587
	}
588
589 2
	/**
590
	 * Prepare value to save.
591 2
	 *
592 2
	 * @return array
593
	 */
594 2
	public function getValuesForSave()
595 2
	{
596 2
		$moduleModel = $this->getModule();
597 2
		$saveFields = $this->getModule()->getFieldsForSave($this);
598 2
		$forSave = $this->getEntityDataForSave();
599 2
600 2
		if ($this->isNew()) {
601 2
			$entityModel = $this->getEntity();
602 2
			$forSave[$entityModel->table_name] = [];
603 2
			foreach ($entityModel->tab_name as $tableName) {
604 2
				if (empty($forSave[$tableName])) {
605 2
					$forSave[$tableName] = [];
606 2
				}
607
			}
608
		} else {
609
			$saveFields = array_intersect($saveFields, array_merge(array_keys($this->changes)));
610
		}
611 2
		if ($name = $moduleModel->getSequenceNumberFieldName()) {
612
			$saveFields[] = $name;
613
		}
614
		foreach ($this->dataForSave as $tableName => $values) {
615
			$forSave[$tableName] = array_merge($forSave[$tableName] ?? [], $values);
616
		}
617
		foreach ($saveFields as $fieldName) {
618
			if ($fieldModel = $moduleModel->getFieldByName($fieldName)) {
619
				$value = $this->get($fieldName);
620 5777
				$uitypeModel = $fieldModel->getUITypeModel();
621
				$uitypeModel->validate($value);
622 5777
				if ('' === $value || null === $value) {
623 5763
					$defaultValue = $fieldModel->getDefaultFieldValue();
624
					if ('' !== $defaultValue) {
625 15
						$value = $defaultValue;
626 15
					} else {
627 15
						$value = $uitypeModel->getDBValue($value, $this);
628 15
					}
629 15
					$this->set($fieldName, $value);
630 15
				}
631 15
				$forSave[$fieldModel->getTableName()][$fieldModel->getColumnName()] = $uitypeModel->convertToSave($value, $this);
632 15
			}
633
		}
634 15
635 15
		return $forSave;
636 15
	}
637
638
	/**
639
	 * Get entity data for save.
640
	 *
641
	 * @return array
642
	 */
643
	public function getEntityDataForSave()
644
	{
645
		$row = [];
646
		$time = date('Y-m-d H:i:s');
647 5809
		if ($this->isNew()) {
648
			$row['setype'] = $this->getModuleName();
649 5809
			$row['users'] = ',' . \App\User::getCurrentUserId() . ',';
650
			$row['smcreatorid'] = $this->isEmpty('created_user_id') ? \App\User::getCurrentUserRealId() : $this->get('created_user_id');
651 5809
			$row['createdtime'] = $this->isEmpty('createdtime') ? $time : $this->get('createdtime');
652 5800
			$this->set('createdtime', $row['createdtime']);
653 5800
			$this->set('created_user_id', $row['smcreatorid']);
654
		}
655 10
		$row['modifiedtime'] = $this->getPreviousValue('modifiedtime') ? $this->get('modifiedtime') : $time;
656 10
		$row['modifiedby'] = $this->getPreviousValue('modifiedby') ? $this->get('modifiedby') : \App\User::getCurrentUserRealId();
657
		$this->set('modifiedtime', $row['modifiedtime']);
658 5809
		$this->set('modifiedby', $row['modifiedby']);
659 5809
		return ['vtiger_crmentity' => $row];
660 5798
	}
661
662 21
	/**
663 21
	 * Add relations on save.
664 21
	 * The main purpose of the function to share relational data in workflow.
665 21
	 *
666 21
	 * @return void
667 21
	 */
668 21
	public function addRelations(): void
669 21
	{
670 21
		$recordId = $this->getId();
671
		if (isset($this->ext['relations']) && \is_array($this->ext['relations'])) {
672 21
			foreach ($this->ext['relations'] as $value) {
673
				if ($reverse = empty($value['reverse'])) {
674
					$relationModel = Vtiger_Relation_Model::getInstance($this->getModule(), Vtiger_Module_Model::getInstance($value['relatedModule']), $value['relationId'] ?? false);
675
				} else {
676
					$relationModel = Vtiger_Relation_Model::getInstance(Vtiger_Module_Model::getInstance($value['relatedModule']), $this->getModule(), $value['relationId'] ?? false);
677
				}
678
				if ($relationModel) {
679
					foreach ($value['relatedRecords'] as $record) {
680
						if ($reverse) {
681
							$relationModel->addRelation($this->getId(), $record, $value['params'] ?? false);
682
						} else {
683
							$relationModel->addRelation($record, $this->getId(), $value['params'] ?? false);
684
						}
685
					}
686
				} else {
687
					\App\Log::warning("Relation model does not exist: {$this->getModuleName()} | relatedModule: {$value['relatedModule']} (relationId: {$value['relationId']})| reverse: $reverse");
688
				}
689
			}
690
		}
691
		if ('link' === \App\Request::_get('createmode') && \App\Request::_has('return_module') && \App\Request::_has('return_id')) {
0 ignored issues
show
Bug introduced by
The method _has() does not exist on App\Request. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

691
		if ('link' === \App\Request::_get('createmode') && \App\Request::/** @scrutinizer ignore-call */ _has('return_module') && \App\Request::_has('return_id')) {
Loading history...
Bug introduced by
The method _get() does not exist on App\Request. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

691
		if ('link' === \App\Request::/** @scrutinizer ignore-call */ _get('createmode') && \App\Request::_has('return_module') && \App\Request::_has('return_id')) {
Loading history...
692
			Vtiger_Relation_Model::getInstance(Vtiger_Module_Model::getInstance(\App\Request::_get('return_module')), $this->getModule())
693
				->addRelation(\App\Request::_getInteger('return_id'), $recordId);
694
		}
695
	}
696
697
	/**
698
	 * Function to delete the current Record Model.
699
	 */
700
	public function delete()
701
	{
702
		$db = \App\Db::getInstance();
703
		$transaction = $db->beginTransaction();
704
		try {
705
			$moduleName = $this->getModuleName();
706
			$eventHandler = new App\EventHandler();
707
			$eventHandler->setRecordModel($this);
708
			$eventHandler->setModuleName($moduleName);
709
			$eventHandler->trigger('EntityBeforeDelete');
710
			$db->createCommand()->delete('vtiger_crmentity', ['crmid' => $this->getId()])->execute();
711
			\App\Db::getInstance('admin')->createCommand()->delete('s_#__privileges_updater', ['crmid' => $this->getId()])->execute();
712
			\App\Fields\File::deleteForRecord($this);
713
			$eventHandler->trigger('EntityAfterDelete');
714
			if ($this->getModule()->isCommentEnabled()) {
715
				(new \App\BatchMethod(['method' => 'ModComments_Module_Model::deleteForRecord', 'params' => [$this->getId()]]))->save();
716
			}
717
			$this->clearPrivilegesCache();
718
			$transaction->commit();
719
		} catch (\Exception $e) {
720
			$transaction->rollBack();
721
			throw $e;
722
		}
723
	}
724
725
	/**
726
	 * Static Function to get the instance of a clean Vtiger Record Model for the given module name.
727
	 *
728
	 * @uses \App\Base::__construct()
729
	 *
730
	 * @param string $moduleName
731
	 *
732
	 * @return $this or Module Specific Record Model instance
733
	 */
734
	public static function getCleanInstance(string $moduleName)
735
	{
736
		if (\App\Cache::staticHas('RecordModelCleanInstance', $moduleName)) {
737
			return clone \App\Cache::staticGet('RecordModelCleanInstance', $moduleName);
738
		}
739
		$focus = CRMEntity::getInstance($moduleName);
740
		$module = Vtiger_Module_Model::getInstance($moduleName);
741 4
		$modelClassName = Vtiger_Loader::getComponentClassName('Model', 'Record', $moduleName);
742
		$instance = new $modelClassName();
743 4
		$instance->setModuleFromInstance($module);
744 2
		$instance->isNew = true;
745
		if (isset($focus->column_fields)) {
746 4
			$instance->setData($focus->column_fields);
747
		}
748
		$instance->setEntity($focus);
749
		\App\Cache::staticSave('RecordModelCleanInstance', $moduleName, clone $instance);
750
		return $instance;
751
	}
752
753
	/**
754 1
	 * Static Function to get the instance of the Vtiger Record Model given the recordid and the module name.
755
	 *
756 1
	 * @uses self::__construct()
757 1
	 *
758
	 * @param int    $recordId
759 1
	 * @param string $module
760
	 *
761
	 * @return $this Module Specific Record Model instance
762
	 */
763
	public static function getInstanceById($recordId, $module = null)
764
	{
765
		if (\is_object($module) && is_a($module, 'Vtiger_Module_Model')) {
766
			$moduleName = $module->get('name');
767
		} elseif (\is_string($module)) {
768
			$module = Vtiger_Module_Model::getInstance($module);
769 1
			$moduleName = $module->get('name');
770
		} elseif (empty($module)) {
771 1
			$moduleName = \App\Record::getType($recordId);
772 1
			$module = Vtiger_Module_Model::getInstance($moduleName);
773
		}
774 1
		$cacheName = "$recordId:$moduleName";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $moduleName does not seem to be defined for all execution paths leading up to this point.
Loading history...
775
		if (\App\Cache::staticHas('RecordModel', $cacheName)) {
776
			return \App\Cache::staticGet('RecordModel', $cacheName);
777
		}
778
		$focus = CRMEntity::getInstance($moduleName);
779
		$focus->id = $recordId;
780
		$focus->retrieveEntityInfo($recordId, $moduleName);
781
		$modelClassName = Vtiger_Loader::getComponentClassName('Model', 'Record', $moduleName);
782
		$instance = new $modelClassName();
783
		$instance->setEntity($focus)->setData($focus->column_fields)->setModuleFromInstance($module);
784 1
		$instance->setId($recordId);
785
		$instance->isNew = false;
786 1
		\App\Cache::staticSave('RecordModel', $cacheName, $instance);
787 1
		return $instance;
788
	}
789 1
790
	public static function getInstanceByEntity($focus, $recordId)
791
	{
792
		$moduleName = $focus->moduleName;
793
		$moduleModel = Vtiger_Module_Model::getInstance($moduleName);
794
795
		$modelClassName = Vtiger_Loader::getComponentClassName('Model', 'Record', $moduleName);
796
		$recordModel = new $modelClassName();
797 5
		$recordModel->setData($focus->column_fields)->setId($recordId)->setModuleFromInstance($moduleModel)->setEntity($focus);
798
799 5
		return $recordModel;
800
	}
801
802 5
	/**
803
	 * Function check if record is viewable.
804
	 *
805 5
	 * @return bool
806
	 */
807
	public function isViewable()
808
	{
809
		if (!isset($this->privileges['isViewable'])) {
810
			$this->privileges['isViewable'] = \App\Privilege::isPermitted($this->getModuleName(), 'DetailView', $this->getId());
811
		}
812
		return $this->privileges['isViewable'];
813
	}
814
815
	/**
816
	 * Function check if record is createable.
817
	 *
818
	 * @return bool
819 2
	 */
820
	public function isCreateable()
821 2
	{
822 1
		if (!isset($this->privileges['isCreateable'])) {
823 1
			$this->privileges['isCreateable'] = $this->getModule()->isPermitted('CreateView');
824 1
		}
825 1
		return $this->privileges['isCreateable'];
826 1
	}
827 1
828
	/**
829
	 * Function check if record is editable.
830
	 *
831
	 * @throws \App\Exceptions\NoPermittedToRecord
832
	 *
833
	 * @return bool
834
	 */
835
	public function isEditable(): bool
836
	{
837
		if (!isset($this->privileges['isEditable'])) {
838
			return $this->privileges['isEditable'] = $this->isPermitted('EditView') && !$this->isBlocked();
839
		}
840
		return $this->privileges['isEditable'];
841
	}
842
843
	/**
844
	 * Function check if record is blocked.
845
	 *
846
	 * @return bool
847
	 */
848
	public function isBlocked(): bool
849
	{
850
		if (!isset($this->privileges['isBlocked'])) {
851 1
			$this->privileges['isBlocked'] = $this->isLockByFields()
852
			|| true === Users_Privileges_Model::checkLockEdit($this->getModuleName(), $this)
853 2
			|| !empty($this->getUnlockFields()) || $this->isReadOnly();
854
		}
855
		return $this->privileges['isBlocked'];
856
	}
857
858
	/**
859
	 * Function to check permission.
860
	 *
861
	 * @param string $action
862
	 *
863
	 * @return bool
864
	 */
865
	public function isPermitted(string $action)
866
	{
867
		if (!isset($this->privileges[$action])) {
868
			return $this->privileges[$action] = \App\Privilege::isPermitted($this->getModuleName(), $action, $this->getId());
869
		}
870
		return $this->privileges[$action];
871
	}
872
873
	/**
874
	 * The function decide about mandatory save record.
875 1
	 *
876
	 * @return bool
877 1
	 */
878 1
	public function isMandatorySave()
879
	{
880
		return !empty($this->dataForSave) || ($this->getModule()->isInventory() && $this->getPreviousInventoryItems());
881 1
	}
882 1
883
	/**
884
	 * Function to check read only record.
885
	 *
886
	 * @return bool
887 1
	 */
888 1
	public function isReadOnly(): bool
889
	{
890
		if (!isset($this->privileges['isReadOnly'])) {
891
			return $this->privileges['isReadOnly'] = !$this->isNew() && \App\Components\InterestsConflict::CHECK_STATUS_CONFLICT === \App\Components\InterestsConflict::check($this->getId(), $this->getModuleName());
892
		}
893
		return $this->privileges['isReadOnly'];
894
	}
895
896 1
	/**
897
	 * The function decide about locking the record by field.
898 1
	 *
899 1
	 * @return bool
900
	 */
901 1
902
	/**
903
	 * @throws \App\Exceptions\NoPermittedToRecord
904
	 *
905
	 * @return mixed
906
	 */
907
	public function isLockByFields()
908
	{
909 1
		if (!isset($this->privileges['isLockByFields'])) {
910
			$isLock = false;
911 1
			$moduleName = $this->getModuleName();
912 1
			$recordId = $this->getId();
913
			$focus = $this->getEntity();
914 1
			$lockFields = $focus->getLockFields();
915
			if ($lockFields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lockFields 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...
916
				$loadData = false;
917
				foreach ($lockFields as $fieldName => $values) {
918
					if (!$this->has($fieldName)) {
919
						$loadData = true;
920
					}
921
				}
922 1
				if ($loadData && $recordId) {
923
					$focus->id = $recordId;
924 1
					$focus->retrieveEntityInfo($recordId, $moduleName);
925 1
					$this->setEntity($focus);
926
				}
927 1
				foreach ($lockFields as $fieldName => $values) {
928
					if (!$this->has($fieldName) && isset($focus->column_fields[$fieldName])) {
929
						parent::set($fieldName, $focus->column_fields[$fieldName]);
930
					}
931
					foreach ($values as $value) {
932
						if ($this->get($fieldName) === $value) {
933
							$isLock = true;
934
							break 2;
935 1
						}
936
					}
937 1
				}
938 1
			}
939
			$this->privileges['isLockByFields'] = $isLock;
940 1
		}
941
		return $this->privileges['isLockByFields'];
942
	}
943
944
	/**
945
	 * Function check if record is to unlock.
946
	 *
947
	 * @return bool
948
	 */
949
	public function isUnlockByFields()
950
	{
951
		if (!isset($this->privileges['Unlock'])) {
952
			$this->privileges['Unlock'] = !$this->isNew() && $this->isPermitted('EditView') && $this->isPermitted('OpenRecord')
953
				&& false === Users_Privileges_Model::checkLockEdit($this->getModuleName(), $this) && !$this->isLockByFields() && !empty($this->getUnlockFields(true));
954
		}
955
		return $this->privileges['Unlock'];
956
	}
957
958
	/**
959
	 * Gets unlock fields.
960
	 *
961
	 * @param bool $isAjaxEditable
962
	 *
963
	 * @return array
964
	 */
965
	public function getUnlockFields($isAjaxEditable = false)
966
	{
967
		$id = $this->getId();
968
		$cacheName = 'UnlockFields' . $isAjaxEditable;
969
		if ($id && \App\Cache::staticHas($cacheName, $id)) {
970
			return \App\Cache::staticGet($cacheName, $id);
971
		}
972
		$lockFields = \App\RecordStatus::getLockStatus($this->getModule()->getName());
0 ignored issues
show
Bug introduced by
$this->getModule()->getName() of type boolean is incompatible with the type string expected by parameter $moduleName of App\RecordStatus::getLockStatus(). ( Ignorable by Annotation )

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

972
		$lockFields = \App\RecordStatus::getLockStatus(/** @scrutinizer ignore-type */ $this->getModule()->getName());
Loading history...
973
		foreach ($lockFields as $fieldName => $values) {
974
			if (!\in_array($this->getValueByField($fieldName), $values) || ($isAjaxEditable && !$this->getField($fieldName)->isAjaxEditable())) {
975
				unset($lockFields[$fieldName]);
976
			}
977
		}
978
		if ($id) {
979
			\App\Cache::staticSave($cacheName, $this->getId(), $lockFields);
980
		}
981
		return $lockFields;
982
	}
983
984
	/**
985
	 * Checking for permission to delete.
986
	 *
987
	 * @return bool
988
	 */
989
	public function privilegeToDelete()
990
	{
991
		if (!isset($this->privileges['Deleted'])) {
992
			$this->privileges['Deleted'] = \App\Privilege::isPermitted($this->getModuleName(), 'Delete', $this->getId()) && false === Users_Privileges_Model::checkLockEdit($this->getModuleName(), $this) && !$this->isLockByFields();
993
		}
994
		return $this->privileges['Deleted'];
995
	}
996
997
	/**
998
	 * Checking for permission to move to trash.
999
	 *
1000
	 * @return bool
1001
	 */
1002
	public function privilegeToMoveToTrash()
1003
	{
1004
		if (!isset($this->privileges['MoveToTrash'])) {
1005
			$this->privileges['MoveToTrash'] = 'Trash' !== \App\Record::getState($this->getId()) && \App\Privilege::isPermitted($this->getModuleName(), 'MoveToTrash', $this->getId());
1006
		}
1007
		return $this->privileges['MoveToTrash'];
1008
	}
1009
1010
	/**
1011
	 * Checking for permission to archive.
1012
	 *
1013
	 * @return bool
1014
	 */
1015
	public function privilegeToArchive()
1016
	{
1017
		if (!isset($this->privileges['Archive'])) {
1018
			$this->privileges['Archive'] = 'Archived' !== \App\Record::getState($this->getId()) && \App\Privilege::isPermitted($this->getModuleName(), 'ArchiveRecord', $this->getId());
1019
		}
1020
		return $this->privileges['Archive'];
1021
	}
1022
1023
	/**
1024
	 * Checking for permission to activate.
1025
	 *
1026
	 * @return bool
1027
	 */
1028
	public function privilegeToActivate()
1029
	{
1030
		if (!isset($this->privileges['Activate'])) {
1031
			$this->privileges['Activate'] = 'Active' !== \App\Record::getState($this->getId()) && \App\Privilege::isPermitted($this->getModuleName(), 'ActiveRecord', $this->getId());
1032
		}
1033
		return $this->privileges['Activate'];
1034
	}
1035
1036
	/**
1037
	 * Funtion to get Duplicate Record Url.
1038
	 *
1039
	 * @return string
1040
	 */
1041
	public function getDuplicateRecordUrl()
1042
	{
1043
		return 'index.php?module=' . $this->getModuleName() . '&view=' . $this->getModule()->getEditViewName() . '&record=' . $this->getId() . '&isDuplicate=true';
1044
	}
1045
1046
	/**
1047
	 * Function to get Display value for RelatedList.
1048
	 *
1049
	 * @param string $fieldName
1050
	 *
1051
	 * @return string
1052
	 */
1053
	public function getRelatedListDisplayValue($fieldName)
1054
	{
1055
		$fieldModel = $this->getModule()->getField($fieldName);
1056
		return $fieldModel->getRelatedListDisplayValue($this->get($fieldName));
1057
	}
1058
1059
	public function getSummaryInfo()
1060
	{
1061
		$moduleName = $this->getModuleName();
1062
		$path = "modules/$moduleName/summary_blocks";
1063
		if (!is_dir($path)) {
1064
			return [];
1065
		}
1066
		$tempSummaryBlocks = [];
1067
		$dir = new DirectoryIterator($path);
1068
		foreach ($dir as $fileinfo) {
1069
			if (!$fileinfo->isDot()) {
1070
				$tmp = explode('.', $fileinfo->getFilename());
1071
				$fullPath = $path . DIRECTORY_SEPARATOR . $tmp[0] . '.php';
1072
				if (file_exists($fullPath)) {
1073
					require_once $fullPath;
1074
					$block = new $tmp[0]();
1075
					if (isset($block->reference) && !\App\Module::isModuleActive($block->reference)) {
1076
						continue;
1077
					}
1078
					$tempSummaryBlocks[$block->sequence] = [
1079
						'name' => $block->name,
1080 2
						'data' => $block->process($this),
1081
						'reference' => $block->reference,
1082 2
						'type' => $block->type ?? false,
1083 2
						'icon' => $block->icon ?? false,
1084 2
					];
1085 2
				}
1086 2
			}
1087 2
		}
1088 2
		ksort($tempSummaryBlocks);
1089 2
		$blockCount = 0;
1090 2
		$summaryBlocks = [];
1091
		foreach ($tempSummaryBlocks as $key => $block) {
1092
			$summaryBlocks[(int) ($blockCount / $this->summaryRowCount)][$key] = $tempSummaryBlocks[$key];
1093 2
			++$blockCount;
1094 2
		}
1095 2
		return $summaryBlocks;
1096
	}
1097 2
1098
	/**
1099
	 * Function to set record module field values.
1100 2
	 *
1101 2
	 * @param self $parentRecordModel
1102 2
	 */
1103 2
	public function setRecordFieldValues($parentRecordModel)
1104
	{
1105
		$mfInstance = Vtiger_MappedFields_Model::getInstanceByModules($parentRecordModel->getModule()->getId(), $this->getModule()->getId());
1106
		if ($mfInstance) {
1107 2
			$defaultInvRow = [];
1108 2
			$params = $mfInstance->get('params');
1109
			if (!empty($params['autofill'])) {
1110
				$fieldsList = array_keys($this->getModule()->getFields());
1111
				$parentFieldsList = array_keys($parentRecordModel->getModule()->getFields());
1112
				$commonFields = array_intersect($fieldsList, $parentFieldsList);
1113
				foreach ($commonFields as $fieldName) {
1114
					if (\App\Field::getFieldPermission($parentRecordModel->getModuleName(), $fieldName)) {
1115
						$value = $parentRecordModel->get($fieldName);
1116
						$this->getField($fieldName)->getUITypeModel()->validate($value);
1117
						$this->set($fieldName, $value);
1118
					}
1119
				}
1120
			}
1121
			if ($parentRecordModel->getModule()->isInventory() && $this->getModule()->isInventory()) {
1122
				$inventoryModel = Vtiger_Inventory_Model::getInstance($this->getModuleName());
1123
				$sourceInv = $parentRecordModel->getInventoryData();
1124
				foreach ($inventoryModel->getFields() as $fieldModel) {
1125
					$defaultInvRow[$fieldModel->getColumnName()] = $fieldModel->getDefaultValue();
1126
				}
1127
			}
1128
1129
			foreach ($mfInstance->getMapping() as $mapp) {
1130
				$fieldTarget = $mapp['target'];
1131
				$fieldSource = $mapp['source'];
1132
				if (!\is_object($fieldTarget) || !\is_object($fieldSource)) {
1133
					continue;
1134
				}
1135
				$type = $mapp['type'];
1136 2
				if ('SELF' == $type && \in_array($parentRecordModel->getModuleName(), $fieldTarget->getReferenceList())) {
1137
					$this->set($fieldTarget->getName(), $parentRecordModel->get($fieldSource->getName()));
1138 2
				} elseif ('INVENTORY' == $type && $sourceInv) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sourceInv does not seem to be defined for all execution paths leading up to this point.
Loading history...
1139 2
					foreach ($sourceInv as $key => $base) {
1140 1
						if (!isset($base[$fieldSource->getName()]) || !($fieldInventory = $inventoryModel->getField($fieldTarget->getName()))) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $inventoryModel does not seem to be defined for all execution paths leading up to this point.
Loading history...
1141 2
							continue;
1142
						}
1143
						$fieldInventory->validate($base[$fieldSource->getName()], $fieldInventory->getColumnName(), false);
1144 2
						if (null === $this->getInventoryItem($key)) {
1145
							$this->inventoryData[$key] = $defaultInvRow;
1146 2
						}
1147 2
						$this->setInventoryItemPart($key, $fieldInventory->getColumnName(), $base[$fieldSource->getName()]);
1148
						foreach (array_keys($fieldInventory->getCustomColumn()) as $customColumn) {
1149
							if (\array_key_exists($customColumn, $base)) {
1150
								$fieldInventory->validate($base[$customColumn], $customColumn, false);
1151
								$this->setInventoryItemPart($key, $customColumn, $base[$customColumn]);
1152
							}
1153
						}
1154
					}
1155
				} elseif (!\in_array($type, ['INVENTORY', 'SELF']) && \App\Field::getFieldPermission($parentRecordModel->getModuleName(), $fieldSource->getName())) {
1156
					$value = $parentRecordModel->get($fieldSource->getName());
1157
					if (!$value) {
1158
						$value = $mapp['default'];
1159 2
					}
1160
					$this->getField($fieldTarget->getName())->getUITypeModel()->validate($value);
1161 2
					$this->set($fieldTarget->getName(), $value);
1162
				}
1163
			}
1164
		}
1165
	}
1166
1167
	public function getListFieldsToGenerate($parentModuleName, $moduleName)
1168
	{
1169
		$moduleInstance = CRMEntity::getInstance($parentModuleName);
1170
1171
		return $moduleInstance->fieldsToGenerate[$moduleName] ? $moduleInstance->fieldsToGenerate[$moduleName] : [];
0 ignored issues
show
Bug introduced by
The property fieldsToGenerate does not seem to exist on CRMEntity.
Loading history...
1172
	}
1173 2
1174
	/**
1175 2
	 * Save the inventory data.
1176 2
	 *
1177 2
	 * @throws \App\Exceptions\AppException
1178 2
	 * @throws \yii\db\Exception
1179 2
	 */
1180 2
	public function saveInventoryData()
1181
	{
1182
		\App\Log::trace('Start ' . __METHOD__);
1183
		$inventoryData = $this->getInventoryData();
1184
		$prevValue = $this->getPreviousInventoryItems();
1185
		if (($this->isNew() && $inventoryData) || (!$this->isNew() && $prevValue)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $prevValue 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...
1186
			$db = App\Db::getInstance();
1187
			$dbCommand = $db->createCommand();
1188
			$inventory = Vtiger_Inventory_Model::getInstance($this->getModuleName());
1189 2
			$tableName = $inventory->getDataTableName();
1190 2
			if ($prevValue && ($ids = array_column($prevValue, 'id'))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $prevValue 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...
1191 2
				$dbCommand->delete($tableName, ['id' => $ids])->execute();
1192
			}
1193
			foreach ($inventoryData as $key => $item) {
1194 2
				foreach ($inventory->getFields() as $field) {
1195 2
					$field->validate($item[$field->getColumnName()] ?? null, $field->getColumnName(), false);
1196 2
				}
1197
				if (isset($item['id'])) {
1198
					$dbCommand->update($tableName, $item, ['id' => $item['id']])->execute();
1199 2
				} else {
1200 2
					$item['crmid'] = $this->getId();
1201 2
					$dbCommand->insert($tableName, $item)->execute();
1202 2
					$item['id'] = $db->getLastInsertID("{$tableName}_id_seq");
1203 2
					$this->inventoryData[$key] = $item;
1204
				}
1205
			}
1206
		}
1207 2
		\App\Log::trace('End ' . __METHOD__);
1208 2
	}
1209
1210
	/**
1211
	 * Function to gets inventory default data fields.
1212
	 *
1213
	 * @return int|string|null
1214
	 */
1215
	public function getInventoryDefaultDataFields()
1216
	{
1217
			$inventoryData = $this->getInventoryData();
1218 1
			$lastItem = end($inventoryData);
1219
		$defaultData = [];
1220 1
			if (!empty($lastItem)) {
1221 1
				$items = ['discountparam', 'currencyparam', 'taxparam', 'taxmode', 'discountmode'];
1222 1
				foreach ($items as $key) {
1223
					$defaultData[$key] = $lastItem[$key] ?? null;
1224
				}
1225
			}
1226
		return $defaultData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $defaultData returns the type array which is incompatible with the documented return type integer|null|string.
Loading history...
1227
	}
1228
1229
	/**
1230
	 * Loading the inventory data.
1231
	 *
1232
	 * @throws \App\Exceptions\AppException
1233 2
	 *
1234
	 * @return array Inventory data
1235 2
	 */
1236
	public function getInventoryData()
1237
	{
1238
		\App\Log::trace('Entering ' . __METHOD__);
1239
		if (!isset($this->inventoryData) && $this->getId()) {
1240
			$this->inventoryData = \Vtiger_Inventory_Model::getInventoryDataById($this->getId(), $this->getModuleName());
1241
		} elseif (!isset($this->inventoryData) && $this->get('record_id')) {
1242 2
			$this->inventoryData = \Vtiger_Inventory_Model::getInventoryDataById($this->get('record_id'), $this->getModuleName());
1243 2
		} else {
1244
			$this->inventoryData = $this->inventoryData ?? [];
1245
		}
1246
		\App\Log::trace('Exiting ' . __METHOD__);
1247
		return $this->inventoryData;
1248
	}
1249
1250
	/**
1251
	 * Gets inventory item.
1252
	 *
1253
	 * @param int|string $key
1254
	 *
1255
	 * @throws \App\Exceptions\AppException
1256
	 *
1257
	 * @return mixed
1258
	 */
1259
	public function getInventoryItem($key)
1260
	{
1261 1
		return $this->getInventoryData()[$key] ?? null;
1262
	}
1263 1
1264 1
	/**
1265
	 * Initialization of inventory data.
1266
	 *
1267
	 * @param array $items
1268
	 * @param bool  $userFormat
1269
	 *
1270
	 * @throws \App\Exceptions\AppException
1271 5794
	 * @throws \App\Exceptions\Security
1272
	 */
1273 5794
	public function initInventoryData(array $items, bool $userFormat = true)
1274
	{
1275
		\App\Log::trace('Entering ' . __METHOD__);
1276
		$inventoryModel = Vtiger_Inventory_Model::getInstance($this->getModuleName());
1277
		$fields = $inventoryModel->getFields();
1278
		$this->getInventoryData();
1279
		$requiredField = $inventoryModel->getField('name');
1280
		if (!$this->isNew()) {
1281
			$newKeys = array_column($items, 'id', 'id');
1282
			foreach ($this->inventoryData as $key => $item) {
1283
				if (!isset($newKeys[$key])) {
1284
					$this->changesInventory[$key] = $item;
1285
					unset($this->inventoryData[$key]);
1286
				}
1287
			}
1288
		}
1289
		$i = 0;
1290
		foreach ($items as $key => $item) {
1291
			if (empty($item['id']) && $requiredField && !$requiredField->isRequired() && !isset($item[$requiredField->getColumnName()])) {
1292
				continue;
1293
			}
1294
			$item['id'] = empty($item['id']) ? '#' . $i++ : (int) $item['id'];
1295
			foreach ($fields as $field) {
1296
				$field->setValueToRecord($this, $item, $userFormat);
1297
			}
1298
		}
1299
		if ($this->isNew() || $this->getPreviousInventoryItems()) {
1300
			foreach ($inventoryModel->getSummaryFields() as $fieldName) {
1301
				if ($this->has('sum_' . $fieldName)) {
1302
					$value = $fields[$fieldName]->getSummaryValuesFromData($this->getInventoryData());
1303
					$this->set('sum_' . $fieldName, $value);
1304
				}
1305
			}
1306
		}
1307
		\App\Log::trace('Exiting ' . __METHOD__);
1308
	}
1309
1310
	/**
1311
	 * Initialization of inventory data from request.
1312
	 *
1313
	 * @param \App\Request $request
1314
	 *
1315
	 * @throws \App\Exceptions\AppException
1316
	 * @throws \App\Exceptions\Security
1317
	 *
1318
	 * @return void
1319
	 */
1320
	public function initInventoryDataFromRequest(App\Request $request): void
1321
	{
1322
		$inventory = Vtiger_Inventory_Model::getInstance($this->getModuleName());
1323
		$rawInventory = $request->getRaw('inventory');
1324
		if (isset($rawInventory['_NUM_'])) {
1325
			unset($rawInventory['_NUM_']);
1326
		}
1327
		if ($inventory->getField('name')) {
1328
			foreach ($rawInventory as $key => $inventoryRow) {
1329
				if (empty($inventoryRow['name'])) {
1330
					unset($rawInventory[$key]);
1331
				}
1332
			}
1333
		}
1334
		$request->set('inventory', $rawInventory, true);
1335
		$this->initInventoryData($request->getMultiDimensionArray('inventory', ['id' => \App\Purifier::INTEGER] + $inventory->getPurifyTemplate()));
1336
	}
1337
1338
	/**
1339
	 * Sets inventory item part.
1340
	 *
1341
	 * @param mixed  $itemId
1342
	 * @param string $name
1343
	 * @param mixed  $value
1344
	 *
1345
	 * @throws \App\Exceptions\AppException
1346
	 */
1347
	public function setInventoryItemPart($itemId, string $name, $value)
1348
	{
1349
		if (!$this->isNew()) {
1350
			if (is_numeric($itemId) && ($prevValue = ($this->getInventoryData()[$itemId][$name] ?? false)) != $value) {
1351
				$this->changesInventory[$itemId][$name] = $prevValue;
1352
			} elseif (!is_numeric($itemId)) {
1353
				$this->changesInventory[$itemId] = [];
1354
			}
1355
		}
1356
		$this->inventoryData[$itemId][$name] = $value;
1357
	}
1358
1359
	/**
1360
	 * Clear privileges.
1361
	 */
1362
	public function clearPrivilegesCache()
1363
	{
1364
		$this->privileges = [];
1365
		Users_Privileges_Model::clearLockEditCache($this->getModuleName() . $this->getId());
1366
		\vtlib\Functions::clearCacheMetaDataRecord($this->getId());
1367
		\App\Cache::staticDelete('UnlockFields', $this->getId());
1368
	}
1369
1370
	/**
1371
	 * Set handler exceptions.
1372
	 *
1373
	 * @param array $exceptions
1374
	 */
1375
	public function setHandlerExceptions($exceptions): void
1376
	{
1377
		$this->handlerExceptions = $exceptions;
1378
	}
1379
1380
	/**
1381
	 * get handler exceptions.
1382
	 *
1383
	 * @return array
1384
	 */
1385
	public function getHandlerExceptions(): array
1386
	{
1387
		return $this->handlerExceptions;
1388
	}
1389
1390
	/**
1391
	 * Function to get the list view actions for the record.
1392
	 *
1393
	 * @return Vtiger_Link_Model[] - Associate array of Vtiger_Link_Model instances
1394
	 */
1395
	public function getRecordListViewLinksRightSide()
1396
	{
1397
		$links = $recordLinks = [];
1398
		if ($this->isEditable() && $this->isCanAssignToHimself()) {
1399
			$recordLinks[] = [
1400
				'linktype' => 'LIST_VIEW_ACTIONS_RECORD_RIGHT_SIDE',
1401
				'linklabel' => 'BTN_ASSIGN_TO_ME',
1402
				'linkurl' => 'javascript:Vtiger_Index_Js.assignToOwner(this)',
1403
				'linkicon' => 'fas fa-user',
1404
				'linkclass' => 'btn-sm btn-success',
1405
				'linkdata' => ['module' => $this->getModuleName(), 'record' => $this->getId()],
1406
			];
1407
		}
1408
		foreach ($recordLinks as $recordLink) {
1409
			$links[] = Vtiger_Link_Model::getInstanceFromValues($recordLink);
1410
		}
1411
		return $links;
1412
	}
1413
1414
	/**
1415
	 * Function to get the list view actions for the record.
1416
	 *
1417
	 * @return Vtiger_Link_Model[] - Associate array of Vtiger_Link_Model instances
1418
	 */
1419
	public function getRecordListViewLinksLeftSide()
1420
	{
1421
		if (!$this->isViewable()) {
1422
			return [];
1423
		}
1424
		$links = $recordLinks = [];
1425
		if ($this->getModule()->isSummaryViewSupported() && array_filter($this->getModule()->getWidgets())) {
1426
		$recordLinks['LBL_SHOW_QUICK_DETAILS'] = [
1427
			'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1428
			'linklabel' => 'LBL_SHOW_QUICK_DETAILS',
1429
			'linkurl' => 'index.php?module=' . $this->getModuleName() . '&view=QuickDetailModal&record=' . $this->getId(),
1430
			'linkicon' => 'far fa-caret-square-right',
1431
			'linkclass' => 'btn-sm btn-default',
1432
			'modalView' => true,
1433
		];
1434
		}
1435
		$recordLinks['LBL_SHOW_COMPLETE_DETAILS'] = [
1436
			'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1437
			'linklabel' => 'LBL_SHOW_COMPLETE_DETAILS',
1438
			'linkurl' => $this->getFullDetailViewUrl(),
1439
			'linkicon' => 'fas fa-th-list',
1440
			'linkclass' => 'btn-sm btn-default',
1441
			'linkhref' => true,
1442
		];
1443
		if ($this->isEditable()) {
1444
			$recordLinks['LBL_EDIT'] = [
1445
				'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1446
				'linklabel' => 'LBL_EDIT',
1447
				'linkurl' => $this->getEditViewUrl(),
1448
				'linkicon' => 'yfi yfi-full-editing-view',
1449
				'linkclass' => 'btn-sm btn-default',
1450
				'linkhref' => true,
1451
			];
1452
			if ($this->getModule()->isQuickCreateSupported()) {
1453
				$recordLinks['LBL_QUICK_EDIT'] = [
1454
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1455
					'linklabel' => 'LBL_QUICK_EDIT',
1456
					'linkicon' => 'yfi yfi-quick-creation',
1457
					'linkclass' => 'btn-sm btn-default js-quick-edit-modal',
1458
					'linkdata' => [
1459
						'module' => $this->getModuleName(),
1460
						'record' => $this->getId(),
1461
					],
1462
				];
1463
			}
1464
			if ($link = \App\Fields\ServerAccess::getLinks($this, 'List')) {
1465
				$recordLinks['BTN_SERVER_ACCESS'] = $link;
1466
			}
1467
		}
1468
		if (!$this->isReadOnly()) {
1469
			if ($this->isViewable() && $this->getModule()->isPermitted('WatchingRecords')) {
1470
				$watching = (int) ($this->isWatchingRecord());
1471
				$recordLinks['BTN_WATCHING_RECORD'] = [
1472
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1473
					'linklabel' => 'BTN_WATCHING_RECORD',
1474
					'linkurl' => 'javascript:Vtiger_Index_Js.changeWatching(this)',
1475
					'linkicon' => 'fas ' . ($watching ? 'fa-eye-slash' : 'fa-eye'),
1476
					'linkclass' => 'btn-sm ' . ($watching ? 'btn-dark' : 'btn-outline-dark'),
1477
					'linkdata' => ['module' => $this->getModuleName(), 'record' => $this->getId(), 'value' => (int) !$watching, 'on' => 'btn-dark', 'off' => 'btn-outline-dark', 'icon-on' => 'fa-eye', 'icon-off' => 'fa-eye-slash'],
1478
				];
1479
			}
1480
			$stateColors = App\Config::search('LIST_ENTITY_STATE_COLOR');
1481
			if ($this->privilegeToActivate()) {
1482
				$recordLinks['LBL_ACTIVATE_RECORD'] = [
1483
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1484
					'linklabel' => 'LBL_ACTIVATE_RECORD',
1485
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=State&state=Active&record=' . $this->getId(),
1486
					'linkicon' => 'fas fa-undo-alt',
1487
					'style' => empty($stateColors['Active']) ? '' : "background: {$stateColors['Active']};",
1488
					'linkdata' => ['confirm' => \App\Language::translate('LBL_ACTIVATE_RECORD_DESC'), 'source-view' => 'List'],
1489
					'linkclass' => 'btn-sm btn-default entityStateBtn js-action-confirm',
1490
				];
1491
			}
1492
			if ($this->privilegeToArchive()) {
1493
				$recordLinks[] = [
1494
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1495
					'linklabel' => 'LBL_ARCHIVE_RECORD',
1496
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=State&state=Archived&record=' . $this->getId(),
1497
					'linkicon' => 'fas fa-archive',
1498
					'style' => empty($stateColors['Archived']) ? '' : "background: {$stateColors['Archived']};",
1499
					'linkdata' => ['confirm' => \App\Language::translate('LBL_ARCHIVE_RECORD_DESC'), 'source-view' => 'List'],
1500
					'linkclass' => 'btn-sm btn-default entityStateBtn js-action-confirm',
1501
				];
1502
			}
1503
			if ($this->privilegeToMoveToTrash()) {
1504
				$recordLinks['LBL_MOVE_TO_TRASH'] = [
1505
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1506
					'linklabel' => 'LBL_MOVE_TO_TRASH',
1507
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=State&state=Trash&record=' . $this->getId(),
1508
					'linkicon' => 'fas fa-trash-alt',
1509
					'style' => empty($stateColors['Trash']) ? '' : "background: {$stateColors['Trash']};",
1510
					'linkdata' => ['confirm' => \App\Language::translate('LBL_MOVE_TO_TRASH_DESC'), 'source-view' => 'List'],
1511
					'linkclass' => 'btn-sm btn-default entityStateBtn js-action-confirm',
1512
				];
1513
			}
1514
			if ($this->privilegeToDelete()) {
1515
				$recordLinks['LBL_DELETE_RECORD_COMPLETELY'] = [
1516
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1517
					'linklabel' => 'LBL_DELETE_RECORD_COMPLETELY',
1518
					'linkicon' => 'fas fa-eraser',
1519
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=Delete&record=' . $this->getId(),
1520
					'linkdata' => ['confirm' => \App\Language::translate('LBL_DELETE_RECORD_COMPLETELY_DESC'), 'source-view' => 'List'],
1521
					'linkclass' => 'btn-sm btn-dark js-action-confirm',
1522
				];
1523
			}
1524
		}
1525
		foreach ($recordLinks as $key => $recordLink) {
1526
			$links[$key] = Vtiger_Link_Model::getInstanceFromValues($recordLink);
1527
		}
1528
		$allRecordListButtons = Vtiger_Link_Model::getAllByType($this->getModule()->getId(), ['LIST_VIEW_BUTTONS']);
1529
		if (!$this->isReadOnly() && isset($allRecordListButtons['LIST_VIEW_BUTTONS'])) {
1530
			foreach ($allRecordListButtons['LIST_VIEW_BUTTONS'] as $recordListButton) {
1531
				$url = $recordListButton->linkurl ?: $recordListButton->dataUrl;
1532
				$queryParams = vtlib\Functions::getQueryParams($url);
1533
				if (property_exists($recordListButton, 'permit') && isset($queryParams['module']) && !\App\Privilege::isPermitted($queryParams['module'], $recordListButton->get('permit'))) {
1534
					continue;
1535
				}
1536
				if (isset($recordListButton->dataUrl)) {
1537
					$recordListButton->dataUrl .= "&sourceModule={$this->getModuleName()}&sourceRecord={$this->getId()}";
1538
				}
1539
				$links[$recordListButton->get('linklabel')] = $recordListButton;
1540
			}
1541
		}
1542
		return \App\Utils::changeSequence($links, App\Config::module($this->getModuleName(), 'recordListViewButtonSequence', []));
1543
	}
1544
1545
	/**
1546
	 * Get the related list view actions for the record.
1547
	 *
1548
	 * @param Vtiger_RelationListView_Model $viewModel
1549
	 *
1550
	 * @return Vtiger_Link_Model[] - Associate array of Vtiger_Link_Model instances
1551
	 */
1552
	public function getRecordRelatedListViewLinksLeftSide(Vtiger_RelationListView_Model $viewModel)
1553
	{
1554
		$links = [];
1555
		if (!$this->isViewable()) {
1556
			return [];
1557
		}
1558
		if ($this->getModule()->isSummaryViewSupported()) {
1559
		$defaultViewName = $viewModel->getParentRecordModel()->getModule()->getDefaultViewName();
1560
		$links['LBL_SHOW_QUICK_DETAILS'] = Vtiger_Link_Model::getInstanceFromValues([
1561
			'linklabel' => 'LBL_SHOW_QUICK_DETAILS',
1562
			'linkhref' => 'ListPreview' !== $defaultViewName,
1563
			'linkurl' => 'index.php?module=' . $this->getModuleName() . '&view=QuickDetailModal&record=' . $this->getId(),
1564
			'linkicon' => 'far fa-caret-square-right',
1565
			'linkclass' => 'btn-sm btn-default',
1566
			'modalView' => true,
1567
		]);
1568
		}
1569
		$links['LBL_SHOW_COMPLETE_DETAILS'] = Vtiger_Link_Model::getInstanceFromValues([
1570
			'linklabel' => 'LBL_SHOW_COMPLETE_DETAILS',
1571
			'linkurl' => $this->getFullDetailViewUrl(),
1572
			'linkhref' => true,
1573
			'linkicon' => 'fas fa-th-list',
1574
			'linkclass' => 'btn-sm btn-default',
1575
		]);
1576
1577
		if (!$this->isReadOnly()) {
1578 1
			$relationModel = $viewModel->getRelationModel();
1579
			if ($relationModel->isEditable() && $this->isEditable()) {
1580 1
				$links['LBL_EDIT'] = Vtiger_Link_Model::getInstanceFromValues([
1581 1
					'linklabel' => 'LBL_EDIT',
1582
					'linkhref' => true,
1583 1
					'linkurl' => $this->getEditViewUrl(),
1584 1
					'linkicon' => 'yfi yfi-full-editing-view',
1585 1
					'linkclass' => 'btn-sm btn-default',
1586 1
				]);
1587 1
				if ($this->getModule()->isQuickCreateSupported()) {
1588 1
					$links['LBL_QUICK_EDIT'] = Vtiger_Link_Model::getInstanceFromValues([
1589 1
						'linklabel' => 'LBL_QUICK_EDIT',
1590 1
						'linkicon' => 'yfi yfi-quick-creation',
1591 1
						'linkclass' => 'btn-sm btn-default js-quick-edit-modal',
1592 1
						'linkdata' => [
1593 1
							'module' => $this->getModuleName(),
1594 1
							'record' => $this->getId(),
1595
						],
1596
					]);
1597
				}
1598 1
				if ($link = \App\Fields\ServerAccess::getLinks($this, 'RelatedList')) {
1599 1
					$links['BTN_SERVER_ACCESS'] = $link;
1600 1
				}
1601 1
			}
1602 1
			if ($this->getModule()->isPermitted('WatchingRecords')) {
1603 1
				$watching = (int) $this->isWatchingRecord();
1604 1
				$links['BTN_WATCHING_RECORD'] = Vtiger_Link_Model::getInstanceFromValues([
1605
					'linklabel' => 'BTN_WATCHING_RECORD',
1606 1
					'linkurl' => 'javascript:Vtiger_Index_Js.changeWatching(this)',
1607 1
					'linkicon' => 'fas ' . ($watching ? 'fa-eye-slash' : 'fa-eye'),
1608 1
					'linkclass' => 'btn-sm ' . ($watching ? 'btn-dark' : 'btn-outline-dark'),
1609 1
					'linkdata' => [
1610
						'module' => $this->getModuleName(),
1611
						'record' => $this->getId(),
1612 1
						'value' => (int) !$watching,
1613
						'on' => 'btn-dark',
1614 1
						'off' => 'btn-outline-dark',
1615
						'icon-on' => 'fa-eye',
1616
						'icon-off' => 'fa-eye-slash', ],
1617
				]);
1618
			}
1619 1
			if ($this->getModule()->isPermitted('ExportPdf')) {
1620
				$handlerClass = Vtiger_Loader::getComponentClassName('Model', 'PDF', $this->getModuleName());
1621
				$pdfModel = new $handlerClass();
1622
				if ($pdfModel->checkActiveTemplates($this->getId(), $this->getModuleName(), 'Detail')) {
1623
					$links['LBL_EXPORT_PDF'] = Vtiger_Link_Model::getInstanceFromValues([
1624
						'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1625
						'linklabel' => 'LBL_EXPORT_PDF',
1626
						'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&view=PDF&fromview=Detail&record=' . $this->getId(),
1627
						'linkicon' => 'fas fa-file-pdf',
1628
						'linkclass' => 'btn-sm btn-outline-danger showModal js-pdf',
1629
					]);
1630
				}
1631
			}
1632
			$privilegeToDelete = $relationModel->privilegeToDelete($this);
1633
			if ($privilegeToDelete) {
1634
				$links['LBL_REMOVE_RELATION'] = Vtiger_Link_Model::getInstanceFromValues([
1635
					'linklabel' => 'LBL_REMOVE_RELATION',
1636
					'linkicon' => 'fas fa-unlink',
1637
					'linkclass' => 'btn-sm btn-secondary relationDelete entityStateBtn',
1638
					'linkdata' => [
1639
						'content' => \App\Language::translate('LBL_REMOVE_RELATION'),
1640
						'confirm' => \App\Language::translate('LBL_REMOVE_RELATION_CONFIRMATION'),
1641
						'id' => $this->getId(),
1642
					],
1643
				]);
1644
			}
1645
			$stateColors = App\Config::search('LIST_ENTITY_STATE_COLOR');
1646
			if ($this->privilegeToActivate()) {
1647
				$links['LBL_ACTIVATE_RECORD'] = Vtiger_Link_Model::getInstanceFromValues([
1648
					'linklabel' => 'LBL_ACTIVATE_RECORD',
1649
					'linkicon' => 'fas fa-undo-alt',
1650
					'linkclass' => 'btn-sm btn-secondary relationDelete entityStateBtn',
1651
					'style' => empty($stateColors['Active']) ? '' : "background: {$stateColors['Active']};",
1652
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=State&state=Active&record=' . $this->getId(),
1653
					'linkdata' => [
1654
						'content' => \App\Language::translate('LBL_ACTIVATE_RECORD'),
1655
						'confirm' => \App\Language::translate('LBL_ACTIVATE_RECORD_DESC'),
1656
						'id' => $this->getId(),
1657
					],
1658
				]);
1659
			}
1660
			if ($this->privilegeToArchive()) {
1661
				$links['LBL_ARCHIVE_RECORD'] = Vtiger_Link_Model::getInstanceFromValues([
1662
					'linklabel' => 'LBL_ARCHIVE_RECORD',
1663
					'linkicon' => 'fas fa-archive',
1664
					'linkclass' => 'btn-sm btn-secondary relationDelete entityStateBtn',
1665
					'style' => empty($stateColors['Archived']) ? '' : "background: {$stateColors['Archived']};",
1666
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=State&state=Archived&record=' . $this->getId(),
1667
					'linkdata' => [
1668
						'content' => \App\Language::translate('LBL_ARCHIVE_RECORD'),
1669
						'confirm' => \App\Language::translate('LBL_ARCHIVE_RECORD_DESC'),
1670
						'id' => $this->getId(),
1671
					],
1672
				]);
1673
			}
1674
			if ($privilegeToDelete && $this->privilegeToMoveToTrash()) {
1675
				$links['LBL_MOVE_TO_TRASH'] = Vtiger_Link_Model::getInstanceFromValues([
1676
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1677
					'linklabel' => 'LBL_MOVE_TO_TRASH',
1678
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=State&state=Trash&record=' . $this->getId(),
1679
					'linkicon' => 'fas fa-trash-alt',
1680
					'style' => empty($stateColors['Trash']) ? '' : "background: {$stateColors['Trash']};",
1681
					'linkdata' => ['confirm' => \App\Language::translate('LBL_MOVE_TO_TRASH_DESC')],
1682
					'linkclass' => 'btn-sm btn-outline-dark relationDelete entityStateBtn',
1683
				]);
1684
			}
1685
			if ($privilegeToDelete && $this->privilegeToDelete()) {
1686
				$links['LBL_DELETE_RECORD_COMPLETELY'] = Vtiger_Link_Model::getInstanceFromValues([
1687
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1688
					'linklabel' => 'LBL_DELETE_RECORD_COMPLETELY',
1689
					'linkicon' => 'fas fa-eraser',
1690
					'dataUrl' => 'index.php?module=' . $this->getModuleName() . '&action=Delete&record=' . $this->getId(),
1691
					'linkdata' => ['confirm' => \App\Language::translate('LBL_DELETE_RECORD_COMPLETELY_DESC')],
1692
					'linkclass' => 'btn-sm btn-dark relationDelete entityStateBtn',
1693
				]);
1694
			}
1695
			if (!empty($relationModel->getTypeRelationModel()->customFields) && $relationModel->getTypeRelationModel()->getFields(true) && ($parentRecord = $relationModel->get('parentRecord')) && $parentRecord->isEditable() && $this->isEditable()) {
0 ignored issues
show
Bug introduced by
The method getFields() does not exist on App\Relation\RelationAbstraction. It seems like you code against a sub-type of App\Relation\RelationAbstraction such as ModComments_GetRelatedRecord_Relation or Occurrences_GetRelatedMembers_Relation. ( Ignorable by Annotation )

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

1695
			if (!empty($relationModel->getTypeRelationModel()->customFields) && $relationModel->getTypeRelationModel()->/** @scrutinizer ignore-call */ getFields(true) && ($parentRecord = $relationModel->get('parentRecord')) && $parentRecord->isEditable() && $this->isEditable()) {
Loading history...
1696
				$changeRelationDataButton = Vtiger_Link_Model::getInstanceFromValues([
1697
					'linktype' => 'LIST_VIEW_ACTIONS_RECORD_LEFT_SIDE',
1698
					'linklabel' => 'LBL_CHANGE_RELATION_DATA',
1699
					'dataUrl' => "index.php?module={$relationModel->getParentModuleModel()->getName()}&view=ChangeRelationData&record={$this->getId()}&fromRecord={$parentRecord->getId()}&relationId={$relationModel->getId()}",
1700
					'linkicon' => 'mdi mdi-briefcase-edit-outline',
1701
					'linkclass' => 'btn-sm btn-warning js-show-modal',
1702
				]);
1703
				if (App\Config::relation('separateChangeRelationButton')) {
1704
					$links['BUTTONS']['LBL_CHANGE_RELATION_DATA'] = $changeRelationDataButton;
1705
				} else {
1706
					$links['LBL_CHANGE_RELATION_DATA'] = $changeRelationDataButton;
1707
				}
1708
			}
1709
			$allRecordListButtons = Vtiger_Link_Model::getAllByType($this->getModule()->getId(), ['RELATED_LIST_VIEW_BUTTONS']);
1710
			if (isset($allRecordListButtons['RELATED_LIST_VIEW_BUTTONS'])) {
1711
				foreach ($allRecordListButtons['RELATED_LIST_VIEW_BUTTONS'] as $recordListButton) {
1712
					$url = $recordListButton->linkurl ?: $recordListButton->dataUrl;
1713
					$queryParams = vtlib\Functions::getQueryParams($url);
1714
					if (property_exists($recordListButton, 'permit') && isset($queryParams['module']) && !\App\Privilege::isPermitted($queryParams['module'], $recordListButton->get('permit'))) {
1715
						continue;
1716
					}
1717
					if (isset($recordListButton->dataUrl)) {
1718
						$recordListButton->dataUrl .= "&sourceModule={$this->getModuleName()}&sourceRecord={$this->getId()}";
1719
						if ($relationModel->get('parentRecord') && ($relationField = $relationModel->getRelationField())) {
1720
							$recordListButton->dataUrl .= '&' . $relationField->getName() . '=' . $relationModel->get('parentRecord')->getId();
1721
						}
1722
					}
1723
					$links[$recordListButton->get('linklabel')] = $recordListButton;
1724
				}
1725
			}
1726
		}
1727
		return \App\Utils::changeSequence($links, App\Config::module($this->getModuleName(), 'recordRelatedListViewButtonSequence', []));
1728
	}
1729
1730
	/**
1731
	 * Function checks if user can assign record to himself.
1732
	 *
1733
	 * @return bool
1734
	 */
1735
	public function isCanAssignToHimself()
1736
	{
1737
		return $this->isPermitted('AssignToYourself') && \App\PrivilegeUtil::MEMBER_TYPE_GROUPS === \App\Fields\Owner::getType($this->getValueByField('assigned_user_id'))
1738
			&& \array_key_exists(\App\User::getCurrentUserId(), \App\Fields\Owner::getInstance($this->getModuleName())->getAccessibleUsers('', 'owner'));
1739
	}
1740
1741
	/**
1742
	 * Function gets the value from this record.
1743
	 *
1744
	 * @param string $fieldName
1745
	 *
1746
	 * @return mixed
1747
	 */
1748
	public function getValueByField(string $fieldName)
1749
	{
1750
		if (!$this->has($fieldName) || '' === $this->get($fieldName)) {
1751
			$focus = $this->getEntity();
1752
			if (isset($focus->column_fields[$fieldName]) && '' !== $focus->column_fields[$fieldName]) {
1753
				$value = $focus->column_fields[$fieldName];
1754
			} else {
1755
				$fieldModel = $this->getModule()->getFieldByName($fieldName);
1756
				$idName = $focus->tab_name_index[$fieldModel->getTableName()];
1757
				$value = \vtlib\Functions::getSingleFieldValue($fieldModel->getTableName(), $fieldModel->getColumnName(), $idName, $this->getId());
1758
			}
1759
			parent::set($fieldName, $value);
1760
		}
1761
		return $this->get($fieldName);
1762
	}
1763
1764
	/**
1765
	 * Function gets the value from this record.
1766
	 *
1767
	 * @param \Vtiger_Field_Model $fieldModel
1768
	 *
1769
	 * @return mixed
1770
	 */
1771
	public function getValueByFieldModel(Vtiger_Field_Model $fieldModel)
1772
	{
1773
		if ($fieldModel->get('source_field_name')) {
1774
			return isset($this->ext[$fieldModel->get('source_field_name')][$fieldModel->getModuleName()]) ? $this->ext[$fieldModel->get('source_field_name')][$fieldModel->getModuleName()]->get($fieldModel->getName()) : null;
1775
		}
1776
		return $this->get($fieldModel->getName());
1777
	}
1778
1779
	/**
1780
	 * Change record state.
1781
	 *
1782
	 * @param type $state
1783
	 */
1784
	public function changeState($state)
1785
	{
1786
		$db = \App\Db::getInstance();
1787
		$transaction = $db->beginTransaction();
1788
		try {
1789
			$this->set('deleted', $state);
1790
			$stateId = 0;
1791
			switch ($state) {
1792
				case 'Active':
1793
					$stateId = 0;
1794
					break;
1795
				case 'Trash':
1796
					$stateId = 1;
1797
					break;
1798
				case 'Archived':
1799
					$stateId = 2;
1800
					break;
1801
				default:
1802
					break;
1803
			}
1804
			$dbCommand = $db->createCommand();
1805
			$dbCommand->update('vtiger_crmentity', [
1806
				'deleted' => $stateId, 'modifiedtime' => date('Y-m-d H:i:s'),
1807
				'modifiedby' => \App\User::getCurrentUserId(),
1808
			], ['crmid' => $this->getId()])->execute();
1809
			if ('Active' !== $state) {
0 ignored issues
show
introduced by
The condition 'Active' !== $state is always true.
Loading history...
1810
				$dbCommand->delete('u_#__crmentity_search_label', ['crmid' => $this->getId()])->execute();
1811
			}
1812
			$eventHandler = new App\EventHandler();
1813
			$eventHandler->setRecordModel($this);
1814
			$eventHandler->setModuleName($this->getModuleName());
1815
			if ($this->getHandlerExceptions()) {
1816
				$eventHandler->setExceptions($this->getHandlerExceptions());
1817
			}
1818
			$eventHandler->trigger('EntityChangeState');
1819
1820
			$transaction->commit();
1821
		} catch (\Exception $e) {
1822
			$transaction->rollBack();
1823
			throw $e;
1824
		}
1825
	}
1826
1827
	/**
1828
	 * Get list view color for record.
1829
	 *
1830
	 * @return string[]
1831
	 */
1832
	public function getListViewColor()
1833
	{
1834
		$colors = [];
1835
		$stateColors = App\Config::search('LIST_ENTITY_STATE_COLOR');
1836
		$state = \App\Record::getState($this->getId());
1837
		if (!empty($stateColors[$state])) {
1838
			$colors['leftBorder'] = $stateColors[$state];
1839
		}
1840
		return $colors;
1841
	}
1842
1843
	/**
1844
	 * Function to get record image.
1845
	 *
1846
	 * @return array
1847
	 */
1848
	public function getImage()
1849
	{
1850
		$image = [];
1851
		if (!$this->isEmpty('imagename') && \App\Json::isJson($this->get('imagename')) && !\App\Json::isEmpty($this->get('imagename'))) {
1852
			$image = \App\Json::decode($this->get('imagename'));
1853
			if (empty($image) || !($image = current($image)) || empty($image['path'])) {
1854
				\App\Log::warning("Problem with data compatibility: No parameter path [{$this->get('imagename')}]");
1855
				return [];
1856
			}
1857
			$image['path'] = ROOT_DIRECTORY . DIRECTORY_SEPARATOR . $image['path'];
1858
			if (file_exists($image['path'])) {
1859
				$image['url'] = "file.php?module={$this->getModuleName()}&action=MultiImage&field=imagename&record={$this->getId()}&key={$image['key']}";
1860
			} else {
1861
				$image = [];
1862
			}
1863
		} else {
1864
			foreach ($this->getModule()->getFieldsByType('multiImage') as $fieldModel) {
1865
				if (!$this->isEmpty($fieldModel->getName()) && \App\Json::isJson($this->get($fieldModel->getName()))) {
1866
					$image = \App\Json::decode($this->get($fieldModel->getName()));
1867
					if (empty($image) || !($image = current($image)) || empty($image['path'])) {
1868
						\App\Log::warning("Problem with data compatibility: No parameter path [{$this->get('imagename')}]");
1869
						return [];
1870
					}
1871
					$image['path'] = ROOT_DIRECTORY . DIRECTORY_SEPARATOR . $image['path'];
1872
					if (file_exists($image['path'])) {
1873
						$image['url'] = "file.php?module={$this->getModuleName()}&action=MultiImage&field={$fieldModel->getName()}&record={$this->getId()}&key={$image['key']}";
1874
						break;
1875
					}
1876
					$image = [];
1877
				}
1878
			}
1879
		}
1880
		return $image;
1881
	}
1882
}
1883