Import_Data_Action::import()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 6
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 Import_Data_Action extends \App\Controller\Action
13
{
14
	public $id;
15
	public $user;
16
	public $module;
17
	public $type;
18
	public $fieldMapping;
19
	public $mergeType;
20
	public $mergeFields;
21
	public $defaultValues = [];
22
	public $importedRecordInfo = [];
23
	protected $inventoryFieldMapData = [];
24
	public $batchImport = true;
25
	public $entitydata = [];
26
27
	const IMPORT_RECORD_NONE = 0;
28
	const IMPORT_RECORD_CREATED = 1;
29
	const IMPORT_RECORD_SKIPPED = 2;
30
	const IMPORT_RECORD_UPDATED = 3;
31
	const IMPORT_RECORD_MERGED = 4;
32
	const IMPORT_RECORD_FAILED = 5;
33
34
	/**
35
	 * Constructor.
36
	 *
37
	 * @param array     $importInfo
38
	 * @param \App\User $user
39
	 */
40
	public function __construct($importInfo, App\User $user)
41
	{
42
		$this->id = $importInfo['id'];
43
		$this->module = $importInfo['module'];
44
		$this->fieldMapping = $importInfo['field_mapping'];
45
		$this->mergeType = $importInfo['merge_type'];
46
		$this->mergeFields = $importInfo['merge_fields'];
47
		$this->defaultValues = $importInfo['default_values'];
48
		$this->user = $user;
49
	}
50
51
	/** {@inheritdoc} */
52
	public function checkPermission(App\Request $request)
53
	{
54
		$currentUserPrivilegesModel = Users_Privileges_Model::getCurrentUserPrivilegesModel();
55
		if (!$currentUserPrivilegesModel->hasModulePermission($request->getModule())) {
0 ignored issues
show
Bug introduced by
$request->getModule() of type string is incompatible with the type integer expected by parameter $mixed of Users_Privileges_Model::hasModulePermission(). ( Ignorable by Annotation )

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

55
		if (!$currentUserPrivilegesModel->hasModulePermission(/** @scrutinizer ignore-type */ $request->getModule())) {
Loading history...
56
			throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406);
57
		}
58
	}
59
60
	/** {@inheritdoc} */
61
	public function process(App\Request $request)
62
	{
63
	}
64
65
	/**
66
	 * Get default field values.
67
	 *
68
	 * @return array
69
	 */
70
	public function getDefaultFieldValues()
71
	{
72
		$key = $this->module . '_' . $this->user->getId();
73
		if (\App\Cache::staticHas('DefaultFieldValues', $key)) {
74
			return \App\Cache::staticGet('DefaultFieldValues', $key);
75
		}
76
77
		$defaultValue = [];
78
		if (!empty($this->defaultValues)) {
79
			if (!\is_array($this->defaultValues)) {
80
				$this->defaultValues = \App\Json::decode($this->defaultValues);
81
			}
82
			if ($this->defaultValues) {
83
				$defaultValue = $this->defaultValues;
84
			}
85
		}
86
		$moduleModel = Vtiger_Module_Model::getInstance($this->module);
87
		foreach ($moduleModel->getMandatoryFieldModels() as $fieldInstance) {
88
			$mandatoryFieldName = $fieldInstance->getName();
89
			if (empty($defaultValue[$mandatoryFieldName])) {
90
				if ('owner' === $fieldInstance->getFieldDataType()) {
91
					$defaultValue[$mandatoryFieldName] = $this->user->getId();
92
				} elseif (!\in_array($fieldInstance->getFieldDataType(), ['datetime', 'date', 'time', 'reference'])) {
93
					$defaultValue[$mandatoryFieldName] = '????';
94
				}
95
			}
96
		}
97
		foreach ($moduleModel->getFields() as $fieldName => $fieldInstance) {
98
			$fieldDefaultValue = $fieldInstance->getDefaultFieldValue();
99
			if (empty($defaultValue[$fieldName])) {
100
				if (52 === $fieldInstance->getUIType()) {
101
					$defaultValue[$fieldName] = $this->user->getId();
102
				} elseif (!empty($fieldDefaultValue)) {
103
					$defaultValue[$fieldName] = $fieldDefaultValue;
104
				}
105
			}
106
		}
107
		\App\Cache::staticSave('DefaultFieldValues', $key, $defaultValue);
108
109
		return $defaultValue;
110
	}
111
112
	/**
113
	 * Get default mandatory field values.
114
	 *
115
	 * @return array
116
	 */
117
	public function getDefaultMandatoryFieldValues()
118
	{
119
		$key = $this->module . '_' . $this->user->getId();
120
		if (\App\Cache::staticHas('DefaultMandatoryFieldValues', $key)) {
121
			return \App\Cache::staticGet('DefaultMandatoryFieldValues', $key);
122
		}
123
		$defaultMandatoryValues = [];
124
		if (!empty($this->defaultValues) && !\is_array($this->defaultValues)) {
125
			$this->defaultValues = \App\Json::decode($this->defaultValues);
126
		}
127
		$moduleModel = Vtiger_Module_Model::getInstance($this->module);
128
		foreach ($moduleModel->getMandatoryFieldModels() as $fieldInstance) {
129
			$mandatoryFieldName = $fieldInstance->getName();
130
			$fieldDefaultValue = $fieldInstance->getDefaultFieldValue();
131
			if (!empty($this->defaultValues[$mandatoryFieldName])) {
132
				$defaultMandatoryValues[$mandatoryFieldName] = $this->defaultValues[$mandatoryFieldName];
133
			} elseif (!empty($fieldDefaultValue)) {
134
				$defaultMandatoryValues[$mandatoryFieldName] = $fieldDefaultValue;
135
			} elseif ('owner' === $fieldInstance->getFieldDataType()) {
136
				$defaultMandatoryValues[$mandatoryFieldName] = $this->user->getId();
137
			} elseif (!\in_array($fieldInstance->getFieldDataType(), ['datetime', 'date', 'time', 'reference'])) {
138
				$defaultMandatoryValues[$mandatoryFieldName] = '????';
139
			}
140
		}
141
		\App\Cache::staticSave('DefaultMandatoryFieldValues', $key, $defaultMandatoryValues);
142
		return $defaultMandatoryValues;
143
	}
144
145
	public function import()
146
	{
147
		if (!$this->initializeImport()) {
148
			return false;
149
		}
150
		$this->importData();
151
		$this->finishImport();
152
	}
153
154
	/**
155
	 * Import data.
156
	 */
157
	public function importData()
158
	{
159
		$this->importRecords();
160
		$this->updateModuleSequenceNumber();
161
	}
162
163
	public function initializeImport()
164
	{
165
		$lockInfo = Import_Lock_Action::isLockedForModule($this->module);
166
		if ($lockInfo) {
167
			if ($lockInfo['userid'] != $this->user->getId()) {
168
				Import_Utils_Helper::showImportLockedError($lockInfo);
169
				return false;
170
			}
171
			return true;
172
		}
173
		Import_Lock_Action::lock($this->id, $this->module, $this->user);
174
		return true;
175
	}
176
177
	public function finishImport()
178
	{
179
		Import_Lock_Action::unLock($this->user, $this->module);
180
		Import_Queue_Action::remove($this->id);
181
	}
182
183
	public function updateModuleSequenceNumber()
184
	{
185
		\App\Fields\RecordNumber::getInstance($this->module)->updateRecords();
186
	}
187
188
	/**
189
	 * Update import status.
190
	 *
191
	 * @param int   $entryId
192
	 * @param array $entityInfo
193
	 */
194
	public function updateImportStatus($entryId, $entityInfo)
195
	{
196
		$tableName = Import_Module_Model::getDbTableName($this->user);
197
		$entityId = $entityInfo['id'] ?? null;
198
		\App\Db::getInstance()->createCommand()->update($tableName, ['temp_status' => $entityInfo['status'], 'recordid' => $entityId], ['id' => $entryId])->execute();
199
	}
200
201
	/**
202
	 * Import records.
203
	 *
204
	 * @return bool
205
	 */
206
	public function importRecords()
207
	{
208
		$moduleName = $this->module;
209
		$tableName = Import_Module_Model::getDbTableName($this->user);
210
211
		$query = new \App\Db\Query();
212
		$query->from($tableName)->where(['temp_status' => self::IMPORT_RECORD_NONE]);
213
		if ($this->batchImport) {
214
			$importBatchLimit = \App\Config::module('Import', 'BATCH_LIMIT');
215
			$query->limit($importBatchLimit);
216
		}
217
218
		$moduleModel = Vtiger_Module_Model::getInstance($moduleName);
219
		$isInventory = $moduleModel->isInventory();
220
		if ($isInventory) {
221
			$inventoryTableName = Import_Module_Model::getInventoryDbTableName($this->user);
222
		}
223
224
		$dataReader = $query->createCommand()->query();
225
		while ($row = $dataReader->read()) {
226
			$rowId = $row['id'];
227
228
			if ($isInventory) {
229
				$inventoryFieldData = (new \App\Db\Query())->from($inventoryTableName)->where(['id' => $rowId])->all();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $inventoryTableName does not seem to be defined for all execution paths leading up to this point.
Loading history...
230
			}
231
232
			$entityInfo = null;
233
			$fieldData = [];
234
			foreach ($this->fieldMapping as $fieldName => $index) {
235
				$fieldData[$fieldName] = \App\Purifier::decodeHtml($row[$fieldName]);
236
			}
237
238
			$mergeTypeValue = $this->mergeType;
239
			$createRecord = false;
240
241
			if (!empty($mergeTypeValue) && Import_Module_Model::AUTO_MERGE_NONE !== $mergeTypeValue) {
242
				$queryGenerator = new App\QueryGenerator($moduleName, $this->user->getId());
243
				$queryGenerator->setFields(['id']);
244
				$moduleFields = $queryGenerator->getModuleFields();
245
				foreach ($this->mergeFields as $index => $mergeField) {
246
					$comparisonValue = $fieldData[$mergeField];
247
					$fieldInstance = $moduleFields[$mergeField];
248
					if ('owner' == $fieldInstance->getFieldDataType()) {
249
						$ownerId = \App\User::getUserIdByName(trim($comparisonValue));
250
						if (empty($ownerId)) {
251
							$ownerId = \App\User::getUserIdByFullName(trim($comparisonValue));
252
						}
253
						if (empty($ownerId)) {
254
							$ownerId = \App\Fields\Owner::getGroupId($comparisonValue);
255
						}
256
						$comparisonValue = $ownerId ?: 0;
257
					}
258
					if ('reference' == $fieldInstance->getFieldDataType()) {
259
						if (strpos($comparisonValue, '::::') > 0) {
260
							$referenceFileValueComponents = explode('::::', $comparisonValue);
261
						} else {
262
							$referenceFileValueComponents = explode(':::', $comparisonValue);
263
						}
264
						if (\count($referenceFileValueComponents) > 1) {
265
							$comparisonValue = trim($referenceFileValueComponents[1]);
266
						}
267
					}
268
					$queryGenerator->addCondition($mergeField, $comparisonValue, 'e');
269
				}
270
				$query = $queryGenerator->createQuery();
271
				$baseRecordId = $query->scalar();
272
				if ($baseRecordId) {
273
					switch ($mergeTypeValue) {
274
						case Import_Module_Model::AUTO_MERGE_IGNORE:
275
							$entityInfo['status'] = self::IMPORT_RECORD_SKIPPED;
276
							if ($row['relation_id'] ?? null) {
277
								$this->addRelation($row['relation_id'], $row['src_record'], Vtiger_Record_Model::getInstanceById($baseRecordId));
0 ignored issues
show
Bug introduced by
$baseRecordId of type string is incompatible with the type integer expected by parameter $recordId of Vtiger_Record_Model::getInstanceById(). ( Ignorable by Annotation )

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

277
								$this->addRelation($row['relation_id'], $row['src_record'], Vtiger_Record_Model::getInstanceById(/** @scrutinizer ignore-type */ $baseRecordId));
Loading history...
278
							}
279
							break;
280
						case Import_Module_Model::AUTO_MERGE_OVERWRITE:
281
							$recordModel = Vtiger_Record_Model::getInstanceById($baseRecordId);
282
							$defaultMandatoryFieldValues = $this->getDefaultMandatoryFieldValues();
283
							$mandatoryFieldNames = array_keys($defaultMandatoryFieldValues);
284
							$forUnset = [];
285
							foreach ($fieldData as $fieldName => &$fieldValue) {
286
								$currentValue = $recordModel->get($fieldName);
287
								if (\in_array($fieldName, $mandatoryFieldNames)) {
288
									if ('' === $fieldValue && '' !== $currentValue && null !== $currentValue) {
289
										$forUnset[] = $fieldName;
290
									} elseif ('' === $fieldValue && ('' === $currentValue || null === $currentValue) && isset($defaultMandatoryFieldValues[$fieldName]) && '' !== $defaultMandatoryFieldValues[$fieldName]) {
291
										$fieldValue = $defaultMandatoryFieldValues[$fieldName];
292
									}
293
								}
294
							}
295
							foreach ($forUnset as $unsetName) {
296
								unset($fieldData[$unsetName]);
297
							}
298
							$fieldData = $this->transformForImport($fieldData);
299
							$this->updateRecordByModel($baseRecordId, $fieldData, $moduleName, $row['relation_id'] ?? 0, $row['src_record'] ?? 0);
0 ignored issues
show
Bug introduced by
$baseRecordId of type string is incompatible with the type integer expected by parameter $record of Import_Data_Action::updateRecordByModel(). ( Ignorable by Annotation )

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

299
							$this->updateRecordByModel(/** @scrutinizer ignore-type */ $baseRecordId, $fieldData, $moduleName, $row['relation_id'] ?? 0, $row['src_record'] ?? 0);
Loading history...
300
							$entityInfo['status'] = self::IMPORT_RECORD_UPDATED;
301
							break;
302
						case Import_Module_Model::AUTO_MERGE_MERGEFIELDS:
303
							// fill out empty values with defaults for all fields
304
							// only if actual record field is empty and file field is empty too
305
							$recordModel = Vtiger_Record_Model::getInstanceById($baseRecordId);
306
							$defaultFieldValues = $this->getDefaultFieldValues();
307
							foreach ($fieldData as $fieldName => &$fieldValue) {
308
								$currentValue = $recordModel->get($fieldName);
309
								if ('' === $fieldValue && ('' === $currentValue || null === $currentValue) && isset($defaultFieldValues[$fieldName]) && '' !== $defaultFieldValues[$fieldName]) {
310
									$fieldValue = $defaultFieldValues[$fieldName];
311
								}
312
							}
313
							// remove empty values - do not modify existing
314
							$fieldData = array_filter($fieldData, function ($fieldValue) {
315
								return '' !== $fieldValue;
316
							});
317
							$fieldData = $this->transformForImport($fieldData);
318
							$this->updateRecordByModel($baseRecordId, $fieldData, $moduleName, $row['relation_id'] ?? 0, $row['src_record'] ?? 0);
319
							$entityInfo['status'] = self::IMPORT_RECORD_MERGED;
320
							break;
321
						case Import_Module_Model::AUTO_MERGE_EXISTINGISPRIORITY:
322
							// fill out empty values with defaults for all fields
323
							// only if actual record field is empty and file field is empty too
324
							$recordModel = Vtiger_Record_Model::getInstanceById($baseRecordId);
325
							$defaultFieldValues = $this->getDefaultFieldValues();
326
							foreach ($fieldData as $fieldName => &$fieldValue) {
327
								$currentValue = $recordModel->get($fieldName);
328
								if (null !== $currentValue && '' !== $currentValue) {
329
									// existing record data is priority - do not override - save only empty record fields
330
									$fieldValue = '';
331
								} elseif ('' === $fieldValue && isset($defaultFieldValues[$fieldName]) && '' !== $defaultFieldValues[$fieldName]) {
332
									$fieldValue = $defaultFieldValues[$fieldName];
333
								}
334
							}
335
							// remove empty values - do not modify existing
336
							$fieldData = array_filter($fieldData, function ($fieldValue) {
337
								return '' !== $fieldValue;
338
							});
339
							$fieldData = $this->transformForImport($fieldData);
340
							$this->updateRecordByModel($baseRecordId, $fieldData, $moduleName, $row['relation_id'] ?? 0, $row['src_record'] ?? 0);
341
							$entityInfo['status'] = self::IMPORT_RECORD_MERGED;
342
							break;
343
						default:
344
							$createRecord = true;
345
							break;
346
					}
347
				} else {
348
					$createRecord = true;
349
				}
350
			} else {
351
				$createRecord = true;
352
			}
353
			if ($createRecord) {
354
				$fieldData = $this->transformForImport($fieldData);
355
				if ($fieldData && $isInventory) {
356
					$inventoryFieldData = $this->transformInventoryForImport($inventoryFieldData);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $inventoryFieldData does not seem to be defined for all execution paths leading up to this point.
Loading history...
357
					$fieldData['inventoryData'] = $inventoryFieldData;
358
				}
359
				if (null === $fieldData) {
360
					$entityInfo = null;
361
				} else {
362
					$entityInfo = $this->createRecordByModel($moduleName, $fieldData, $row['relation_id'] ?? 0, $row['src_record'] ?? 0);
363
				}
364
			}
365
			if (null === $entityInfo) {
366
				$entityInfo = ['id' => null, 'status' => self::IMPORT_RECORD_FAILED];
367
			}
368
			$this->importedRecordInfo[$rowId] = $entityInfo;
369
			$this->updateImportStatus($rowId, $entityInfo);
370
		}
371
		$dataReader->close();
372
373
		return true;
374
	}
375
376
	/**
377
	 * Transform inventory for import.
378
	 *
379
	 * @param $inventoryData
380
	 *
381
	 * @throws \App\Exceptions\AppException
382
	 *
383
	 * @return mixed
384
	 */
385
	public function transformInventoryForImport($inventoryData)
386
	{
387
		$inventoryModel = Vtiger_Inventory_Model::getInstance($this->module);
388
		$inventoryFields = $inventoryModel->getFields();
389
		$maps = $inventoryModel->getAutoCompleteFields();
390
391
		foreach ($inventoryData as &$data) {
392
			$this->currentInventoryRawData = $data;
0 ignored issues
show
Bug Best Practice introduced by
The property currentInventoryRawData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
393
			unset($data['id']);
394
			foreach ($data as $fieldName => &$value) {
395
				if (isset($inventoryFields[$fieldName])) {
396
					$fieldInstance = $inventoryFields[$fieldName];
397
					if (\in_array($fieldInstance->getType(), ['Name', 'Reference'])) {
398
						$value = $this->transformInventoryReference($value);
399
					} elseif ('Currency' == $fieldInstance->getType()) {
400
						$value = \App\Fields\Currency::getCurrencyIdByName($value);
401
						$currencyParam = $data['currencyparam'];
402
						$currencyParam = $fieldInstance->getCurrencyParam([], $currencyParam);
0 ignored issues
show
Bug introduced by
The method getCurrencyParam() does not exist on Vtiger_Basic_InventoryField. It seems like you code against a sub-type of Vtiger_Basic_InventoryField such as Vtiger_Currency_InventoryField. ( Ignorable by Annotation )

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

402
						/** @scrutinizer ignore-call */ 
403
      $currencyParam = $fieldInstance->getCurrencyParam([], $currencyParam);
Loading history...
403
						$newCurrencyParam = [];
404
						foreach ($currencyParam as $key => $currencyData) {
405
							$valueData = \App\Fields\Currency::getCurrencyIdByName($key);
406
							if ($valueData) {
407
								$newCurrencyParam[$valueData] = $currencyData;
408
							}
409
						}
410
						$data['currencyparam'] = \App\Json::encode($newCurrencyParam);
411
					} elseif (\array_key_exists($fieldName, $maps)) {
412
						$value = $this->transformInventoryFieldFromMap($value, $maps[$fieldName]);
413
					}
414
				}
415
			}
416
		}
417
		$this->currentInventoryRawData = [];
418
419
		return $inventoryData;
420
	}
421
422
	/**
423
	 * Transform inventory field from map.
424
	 *
425
	 * @param $value
426
	 * @param $mapData
427
	 *
428
	 * @return false|int|string
429
	 */
430
	public function transformInventoryFieldFromMap($value, $mapData)
431
	{
432
		if (!empty($value)) {
433
			if ($this->currentInventoryRawData['name']) {
434
				[$entityName] = $this->transformInventoryReference($this->currentInventoryRawData['name'], true);
435
			}
436
			if ($entityName) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $entityName does not seem to be defined for all execution paths leading up to this point.
Loading history...
437
				if ($this->inventoryFieldMapData[$mapData['field']] && $this->inventoryFieldMapData[$mapData['field']][$entityName]) {
438
					$fieldObject = $this->inventoryFieldMapData[$mapData['field']][$entityName];
439
				} else {
440
					$moduleObject = vtlib\Module::getInstance($entityName);
441
					$fieldObject = $moduleObject ? Vtiger_Field_Model::getInstance($mapData['field'], $moduleObject) : null;
0 ignored issues
show
introduced by
$moduleObject is of type vtlib\Module, thus it always evaluated to true.
Loading history...
442
					if (!\is_array($this->inventoryFieldMapData[$mapData['field']])) {
443
						$this->inventoryFieldMapData[$mapData['field']] = [];
444
					}
445
					$this->inventoryFieldMapData[$mapData['field']][$entityName] = $fieldObject;
446
				}
447
				if ($fieldObject) {
448
					if ('picklist' === $fieldObject->getFieldDataType()) {
449
						$picklist = $fieldObject->getValuesName();
450
						if (\in_array($value, $picklist)) {
451
							$value = array_search($value, $picklist);
452
						} elseif (!\array_key_exists($value, $picklist)) {
453
							$value = '';
454
						}
455
					}
456
				} else {
457
					$value = '';
458
				}
459
			}
460
		}
461
		return $value;
462
	}
463
464
	/**
465
	 * Function transforms value for reference type field.
466
	 *
467
	 * @param \Vtiger_Field_Model $fieldInstance
468
	 * @param string              $fieldValue
469
	 * @param mixed               $value
470
	 * @param mixed               $getArray
471
	 *
472
	 * @return mixed
473
	 */
474
	public function transformInventoryReference($value, $getArray = false)
475
	{
476
		$value = trim($value);
477
		if (!empty($value)) {
478
			if (strpos($value, '::::') > 0) {
479
				$fieldValueDetails = explode('::::', $value);
480
			} elseif (strpos($value, ':::') > 0) {
481
				$fieldValueDetails = explode(':::', $value);
482
			}
483
			if (\is_array($fieldValueDetails) && \count($fieldValueDetails) > 1) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fieldValueDetails does not seem to be defined for all execution paths leading up to this point.
Loading history...
484
				$referenceModuleName = trim($fieldValueDetails[0]);
485
				$entityLabel = trim($fieldValueDetails[1]);
486
				$value = \App\Record::getCrmIdByLabel($referenceModuleName, $entityLabel);
487
			}
488
		}
489
		return $getArray ? [$referenceModuleName, $value] : $value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $referenceModuleName does not seem to be defined for all execution paths leading up to this point.
Loading history...
490
	}
491
492
	/**
493
	 * Function parses data to import.
494
	 *
495
	 * @param array $fieldData
496
	 *
497
	 * @return array
498
	 */
499
	public function transformForImport($fieldData)
500
	{
501
		$moduleModel = Vtiger_Module_Model::getInstance($this->module);
502
		foreach ($fieldData as $fieldName => $fieldValue) {
503
			$fieldInstance = $moduleModel->getFieldByName($fieldName);
504
			$fieldData[$fieldName] = $fieldInstance->getUITypeModel()->getValueFromImport($fieldValue, $this->defaultValues[$fieldName] ?? null);
505
		}
506
		return $fieldData;
507
	}
508
509
	/**
510
	 * Get import status count.
511
	 *
512
	 * @return int
513
	 */
514
	public function getImportStatusCount()
515
	{
516
		$statusCount = ['TOTAL' => 0, 'IMPORTED' => 0, 'FAILED' => 0, 'PENDING' => 0,
517
			'CREATED' => 0, 'SKIPPED' => 0, 'UPDATED' => 0, 'MERGED' => 0, ];
518
		$tableName = Import_Module_Model::getDbTableName($this->user);
519
		$query = (new \App\Db\Query())->select(['temp_status'])->from($tableName);
520
		$dataReader = $query->createCommand()->query();
521
		while (false !== ($status = $dataReader->readColumn(0))) {
522
			++$statusCount['TOTAL'];
523
			++$statusCount['IMPORTED'];
524
			switch ($status) {
525
				case self::IMPORT_RECORD_NONE:
526
					++$statusCount['PENDING'];
527
					--$statusCount['IMPORTED'];
528
					break;
529
				case self::IMPORT_RECORD_FAILED:
530
					++$statusCount['FAILED'];
531
					--$statusCount['IMPORTED'];
532
					break;
533
				case self::IMPORT_RECORD_CREATED:
534
					++$statusCount['CREATED'];
535
					break;
536
				case self::IMPORT_RECORD_SKIPPED:
537
					++$statusCount['SKIPPED'];
538
					break;
539
				case self::IMPORT_RECORD_UPDATED:
540
					++$statusCount['UPDATED'];
541
					break;
542
				case self::IMPORT_RECORD_MERGED:
543
					++$statusCount['MERGED'];
544
					break;
545
				default:
546
					break;
547
			}
548
		}
549
		$dataReader->close();
550
551
		return $statusCount;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statusCount returns the type array<string,integer> which is incompatible with the documented return type integer.
Loading history...
552
	}
553
554
	/**
555
	 * Function run scheduled import and send email.
556
	 */
557
	public static function runScheduledImport()
558
	{
559
		$scheduledImports = self::getScheduledImport();
560
		foreach ($scheduledImports as $importDataController) {
561
			$importDataController->batchImport = false;
562
			if (!$importDataController->initializeImport()) {
563
				continue;
564
			}
565
			App\User::setCurrentUserId($importDataController->user->getId());
566
			$importDataController->importData();
567
			$importStatusCount = $importDataController->getImportStatusCount();
568
			\App\Mailer::sendFromTemplate([
569
				'to' => [$importDataController->user->getDetail('email1') => $importDataController->user->getName()],
570
				'template' => 'ImportCron',
571
				'imported' => $importStatusCount['IMPORTED'],
572
				'total' => $importStatusCount['TOTAL'],
573
				'created' => $importStatusCount['CREATED'],
574
				'updated' => $importStatusCount['UPDATED'],
575
				'merged' => $importStatusCount['MERGED'],
576
				'skipped' => $importStatusCount['SKIPPED'],
577
				'failed' => $importStatusCount['FAILED'],
578
				'module' => App\Language::translate($importDataController->module, $importDataController->module),
579
			]);
580
			$importDataController->finishImport();
581
		}
582
	}
583
584
	public static function getScheduledImport()
585
	{
586
		$scheduledImports = [];
587
		$importQueue = Import_Queue_Action::getAll(Import_Queue_Action::$IMPORT_STATUS_SCHEDULED);
588
		foreach ($importQueue as $importId => $importInfo) {
589
			$scheduledImports[$importId] = new self($importInfo, \App\User::getUserModel($importInfo['user_id']));
590
		}
591
		return $scheduledImports;
592
	}
593
594
	/**
595
	 *  Function to get Record details of import.
596
	 *
597
	 * @parms \App\User $user Current Users
598
	 * @parms string $forModule Imported module
599
	 * @returns array Import Records with the list of skipped records and failed records
600
	 *
601
	 * @param \App\User $user
602
	 * @param mixed     $forModule
603
	 */
604
	public static function getImportDetails(App\User $user, $forModule)
605
	{
606
		$db = App\Db::getInstance();
607
		$importRecords = [];
608
		$tableName = Import_Module_Model::getDbTableName($user);
609
		$query = new \App\Db\Query();
610
		$query->from($tableName)->where(['temp_status' => [self::IMPORT_RECORD_SKIPPED, self::IMPORT_RECORD_FAILED]]);
611
		$dataReader = $query->createCommand()->query();
612
		if ($dataReader->count()) {
613
			$moduleModel = Vtiger_Module_Model::getInstance($forModule);
614
			$columnNames = $db->getTableSchema($tableName, true)->getColumnNames();
615
			foreach ($columnNames as $key => $fieldName) {
616
				if ($key > 2) {
617
					$importRecords['headers'][$fieldName] = $moduleModel->getFieldByName($fieldName)->getFieldLabel();
0 ignored issues
show
Deprecated Code introduced by
The function Vtiger_Field_Model::getFieldLabel() has been deprecated: 7.0 Use $this->getLabel() ( Ignorable by Annotation )

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

617
					$importRecords['headers'][$fieldName] = /** @scrutinizer ignore-deprecated */ $moduleModel->getFieldByName($fieldName)->getFieldLabel();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
618
				}
619
			}
620
			while ($row = $dataReader->read()) {
621
				$record = \Vtiger_Record_Model::getCleanInstance($forModule);
622
				foreach ($importRecords['headers'] as $columnName => $header) {
623
					$record->set($columnName, $row[$columnName]);
624
				}
625
				if (self::IMPORT_RECORD_SKIPPED === (int) $row['temp_status']) {
626
					$importRecords['skipped'][] = $record;
627
				} else {
628
					$importRecords['failed'][] = $record;
629
				}
630
			}
631
			$dataReader->close();
632
		}
633
		return $importRecords;
634
	}
635
636
	/**
637
	 * Get import record status.
638
	 *
639
	 * @param string $value
640
	 *
641
	 * @return int
642
	 */
643
	public function getImportRecordStatus($value)
644
	{
645
		$temp_status = '';
646
		switch ($value) {
647
			case 'created':
648
				$temp_status = self::IMPORT_RECORD_CREATED;
649
				break;
650
			case 'skipped':
651
				$temp_status = self::IMPORT_RECORD_SKIPPED;
652
				break;
653
			case 'updated':
654
				$temp_status = self::IMPORT_RECORD_UPDATED;
655
				break;
656
			case 'merged':
657
				$temp_status = self::IMPORT_RECORD_MERGED;
658
				break;
659
			case 'failed':
660
				$temp_status = self::IMPORT_RECORD_FAILED;
661
				break;
662
			case 'none':
663
				$temp_status = self::IMPORT_RECORD_NONE;
664
				break;
665
			default:
666
				break;
667
		}
668
		return $temp_status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $temp_status also could return the type string which is incompatible with the documented return type integer.
Loading history...
669
	}
670
671
	/**
672
	 * Create record.
673
	 *
674
	 * @param string   $moduleName
675
	 * @param array    $fieldData
676
	 * @param int|null $relationId
677
	 * @param int|null $sourceId
678
	 *
679
	 * @return array|null
680
	 */
681
	public function createRecordByModel($moduleName, $fieldData, ?int $relationId = 0, ?int $sourceId = 0)
682
	{
683
		$recordModel = Vtiger_Record_Model::getCleanInstance($moduleName);
684
		if (isset($fieldData['inventoryData'])) {
685
			$inventoryData = $fieldData['inventoryData'];
686
			unset($fieldData['inventoryData']);
687
		}
688
		if (!empty($inventoryData)) {
689
			$recordModel->initInventoryData($inventoryData, false);
690
		}
691
		foreach ($fieldData as $fieldName => &$value) {
692
			$recordModel->set($fieldName, $value);
693
		}
694
		$recordModel->save();
695
		$ID = $recordModel->getId();
696
		if (!empty($ID)) {
697
			if ($relationId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $relationId 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...
698
				$this->addRelation($relationId, $sourceId, $recordModel);
699
			}
700
			return ['id' => $ID, 'status' => self::IMPORT_RECORD_CREATED];
701
		}
702
		return null;
703
	}
704
705
	/**
706
	 * Add relation.
707
	 *
708
	 * @param int                 $relationId
709
	 * @param int|null            $sourceId
710
	 * @param Vtiger_Record_Model $recordModel
711
	 */
712
	public function addRelation(int $relationId, int $sourceId, Vtiger_Record_Model $recordModel)
713
	{
714
		$relationModel = Vtiger_Relation_Model::getInstanceById($relationId);
715
		$sourceRecord = \App\Record::isExists($sourceId) ? Vtiger_Record_Model::getInstanceById($sourceId) : null;
716
		if ($relationModel
717
			&& $sourceRecord && $sourceRecord->isViewable()
718
			&& $relationModel->getRelationModuleName() === $this->module
719
			&& $relationModel->getParentModuleModel()->getName() === $sourceRecord->getModuleName()
0 ignored issues
show
introduced by
The condition $relationModel->getParen...Record->getModuleName() is always false.
Loading history...
720
		) {
721
			$relationModel->addRelation($sourceRecord->getId(), $recordModel->getId());
722
		}
723
	}
724
725
	/**
726
	 * Update record.
727
	 *
728
	 * @param int      $record
729
	 * @param array    $fieldData
730
	 * @param string   $moduleName
731
	 * @param int|null $relationId
732
	 * @param int|null $sourceId
733
	 */
734
	public function updateRecordByModel($record, $fieldData, $moduleName = false, ?int $relationId = 0, ?int $sourceId = 0)
735
	{
736
		$recordModel = Vtiger_Record_Model::getInstanceById($record, $moduleName);
737
		if (isset($fieldData['inventoryData'])) {
738
			if ($fieldData['inventoryData']) {
739
				$recordModel->initInventoryData($fieldData['inventoryData'], false);
740
			}
741
			unset($fieldData['inventoryData']);
742
		}
743
744
		foreach ($fieldData as $fieldName => &$value) {
745
			$recordModel->set($fieldName, $value);
746
		}
747
		$recordModel->save();
748
		if ($relationId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $relationId 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...
749
			$this->addRelation($relationId, $sourceId, $recordModel);
750
		}
751
	}
752
}
753