Passed
Push — developer ( a71332...bcab7f )
by Radosław
16:27
created

Vtiger_ExportToXml_Model::exportData()   B

Complexity

Conditions 7
Paths 20

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
eloc 19
c 0
b 0
f 0
dl 0
loc 24
rs 8.8333
ccs 0
cts 12
cp 0
cc 7
nc 20
nop 0
crap 56
1
<?php
2
3
/**
4
 * Export to XML model file.
5
 *
6
 * @package Model
7
 *
8
 * @copyright YetiForce S.A.
9
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 * @author    Mariusz Krzaczkowski <[email protected]>
12
 */
13
14
/**
15
 * Export to XML model class.
16
 */
17
class Vtiger_ExportToXml_Model extends \App\Export\ExportRecords
18
{
19
	protected $attrList = ['crmfield', 'crmfieldtype', 'partvalue', 'constvalue', 'refmoule', 'spec', 'refkeyfld', 'delimiter', 'testcondition'];
20
	protected $product = false;
21
	protected $tplName = '';
22
	protected $tmpXmlPath = '';
23
	protected $inventoryFields;
24
	protected $fileExtension = 'xml';
25
26
	/**
27
	 * Set template.
28
	 *
29
	 * @param string $tplName
30
	 *
31
	 * @return $this
32
	 */
33
	public function setTemplate(string $tplName)
34
	{
35
		$this->tplName = $tplName;
36
37
		return $this;
38
	}
39
40
	/** {@inheritdoc} */
41
	public function exportData()
42
	{
43
		$fileName = str_replace(' ', '_', \App\Purifier::decodeHtml(\App\Language::translate($this->moduleName, $this->moduleName)));
44
		$entriesInventory = [];
45
		$addInventoryData = $this->fullData && $this->moduleInstance->isInventory();
46
		$count = 0;
47
		$dataReader = $this->getExportQuery()->createCommand()->query();
48
		while ($row = $dataReader->read()) {
49
			$this->tmpXmlPath = 'cache/import/' . uniqid() . '_.xml';
50
			$this->xmlList[] = $this->tmpXmlPath;
0 ignored issues
show
Bug Best Practice introduced by
The property xmlList does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
51
			if ($addInventoryData) {
52
				$entriesInventory = $this->getEntriesInventory($row) ?: [];
53
			}
54
			if ($this->tplName) {
55
				$this->createXmlFromTemplate($row, $entriesInventory);
56
			} else {
57
				$this->createXml($this->sanitizeValues($row), $entriesInventory);
58
			}
59
			++$count;
60
		}
61
		if (1 < $count) {
62
			$this->outputZipFile($fileName);
63
		} else {
64
			$this->outputFile($fileName);
65
		}
66
	}
67
68
	/**
69
	 * Function returns data from advanced block.
70
	 *
71
	 * @param array $recordData
72
	 *
73
	 * @return array
74
	 */
75
	public function getEntriesInventory($recordData): array
76
	{
77
		$entries = [];
78
		$inventoryModel = Vtiger_Inventory_Model::getInstance($this->moduleName);
79
		$this->inventoryFields = $inventoryModel->getFields();
80
		$table = $inventoryModel->getDataTableName();
81
		$dataReader = (new \App\Db\Query())->from($table)->where(['crmid' => $recordData['id']])->orderBy(['seq' => SORT_ASC])->createCommand()->query();
82
		while ($inventoryRow = $dataReader->read()) {
83
			$entries[] = $inventoryRow;
84
		}
85
		$dataReader->close();
86
87
		return $entries;
88
	}
89
90
	/**
91
	 * Sanitize inventory value.
92
	 *
93
	 * @param mixed  $value
94
	 * @param string $columnName
95
	 *
96
	 * @return string
97
	 */
98
	public function sanitizeInventoryValue($value, $columnName): string
99
	{
100
		if ($field = $this->inventoryFields[$columnName] ?? false) {
101
			if (\in_array($field->getType(), ['Name', 'Reference'])) {
102
				$value = trim($value);
103
				if (!empty($value)) {
104
					$recordModule = \App\Record::getType($value);
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type integer expected by parameter $recordId of App\Record::getType(). ( Ignorable by Annotation )

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

104
					$recordModule = \App\Record::getType(/** @scrutinizer ignore-type */ $value);
Loading history...
105
					$displayValue = \App\Record::getLabel($value);
106
					if (!empty($recordModule) && !empty($displayValue)) {
107
						$value = $recordModule . '::::' . $displayValue;
0 ignored issues
show
Bug introduced by
Are you sure $displayValue of type array|mixed can be used in concatenation? ( Ignorable by Annotation )

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

107
						$value = $recordModule . '::::' . /** @scrutinizer ignore-type */ $displayValue;
Loading history...
108
					} else {
109
						$value = '';
110
					}
111
				} else {
112
					$value = '';
113
				}
114
			} elseif ('Currency' === $field->getType()) {
115
				$value = $field->getDisplayValue($value);
116
			}
117
		} elseif (\in_array($columnName, ['taxparam', 'discountparam', 'currencyparam'])) {
118
			if ('currencyparam' === $columnName) {
119
				$field = $this->inventoryFields['currency'];
120
				$valueData = $field->getCurrencyParam([], $value);
121
				$valueNewData = [];
122
				foreach ($valueData as $currencyId => &$data) {
123
					$currencyName = \App\Fields\Currency::getById($currencyId)['currency_name'];
124
					$data['value'] = $currencyName;
125
					$valueNewData[$currencyName] = $data;
126
				}
127
				$value = \App\Json::encode($valueNewData);
128
			}
129
		}
130
		return html_entity_decode($value);
131
	}
132
133
	public function outputFile($fileName)
134
	{
135
		header("content-disposition: attachment;filename=$fileName.xml");
136
		header('content-type: text/csv;charset=UTF-8');
137
		header('expires: Mon, 31 Dec 2000 00:00:00 GMT');
138
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
139
		header('cache-control: post-check=0, pre-check=0', false);
140
141
		readfile($this->tmpXmlPath);
142
		unlink($this->tmpXmlPath);
143
	}
144
145
	protected function outputZipFile($fileName)
146
	{
147
		$zipName = 'cache/import/' . uniqid() . '.zip';
148
149
		$zip = new ZipArchive();
150
		$zip->open($zipName, ZipArchive::CREATE);
151
		$countXmlList = \count($this->xmlList);
152
		for ($i = 0; $i < $countXmlList; ++$i) {
153
			$xmlFile = basename($this->xmlList[$i]);
154
			$xmlFile = explode('_', $xmlFile);
155
			array_shift($xmlFile);
156
			$xmlFile = $fileName . $i . implode('_', $xmlFile);
157
			$zip->addFile($this->xmlList[$i], $xmlFile);
158
		}
159
		$zip->close();
160
161
		header("content-disposition: attachment;filename=$fileName.zip");
162
		header('content-type: application/zip');
163
		header('expires: Mon, 31 Dec 2000 00:00:00 GMT');
164
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
165
		header('cache-control: post-check=0, pre-check=0', false);
166
		readfile($zipName);
167
		unlink($zipName);
168
		array_map('unlink', $this->xmlList);
169
	}
170
171
	/**
172
	 * Undocumented function.
173
	 *
174
	 * @param array $entries
175
	 * @param array $entriesInventory
176
	 *
177
	 * @return void
178
	 */
179
	public function createXml($entries, $entriesInventory)
180
	{
181
		$exportBlockName = \App\Config::component('Export', 'BLOCK_NAME');
182
		$xml = new \XMLWriter();
183
		$xml->openMemory();
184
		$xml->setIndent(true);
185
		$xml->startDocument('1.0', 'UTF-8');
186
		$xml->startElement('MODULE_FIELDS');
187
		foreach ($this->fields as $fieldName => $fieldModel) {
188
			if ($fieldModel->get('source_field_name')) {
189
				continue;
190
			}
191
			$xml->startElement($fieldName);
192
			$header = \App\Language::translate(\App\Purifier::decodeHtml($fieldModel->get('label')), $this->moduleName);
193
			if ($exportBlockName) {
194
				$header = \App\Language::translate(\App\Purifier::decodeHtml($fieldModel->getBlockName()), $this->moduleName) . '::' . $header;
195
			}
196
			$xml->writeAttribute('type', $fieldModel->getFieldDataType());
197
			$xml->writeAttribute('label', $header);
198
			if ($this->isCData($fieldName)) {
199
				$xml->writeCData($entries[$fieldName]);
200
			} else {
201
				$xml->text($entries[$fieldName]);
202
			}
203
			$xml->endElement();
204
		}
205
		if ($entriesInventory) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entriesInventory 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...
206
			$customColumns = [];
207
			$xml->startElement('INVENTORY_ITEMS');
208
			foreach ($entriesInventory as $inventory) {
209
				unset($inventory['id'], $inventory['crmid']);
210
211
				$xml->startElement('INVENTORY_ITEM');
212
				foreach ($inventory as $columnName => $value) {
213
					$xml->startElement($columnName);
214
					if ($fieldModel = ($this->inventoryFields[$columnName] ?? false)) {
215
						$xml->writeAttribute('label', \App\Language::translate(html_entity_decode($fieldModel->get('label'), ENT_QUOTES), $this->moduleName));
216
						if (!\in_array($columnName, $customColumns)) {
217
							foreach ($fieldModel->getCustomColumn() as $key => $dataType) {
218
								$customColumns[$key] = $columnName;
219
							}
220
						}
221
					}
222
					if ($this->isCData($columnName, $customColumns)) {
223
						$xml->writeCData($this->sanitizeInventoryValue($value, $columnName));
224
					} else {
225
						$xml->text($this->sanitizeInventoryValue($value, $columnName));
226
					}
227
					$xml->endElement();
228
				}
229
				$xml->endElement();
230
			}
231
			$xml->endElement();
232
		}
233
		$xml->endElement();
234
		file_put_contents($this->tmpXmlPath, $xml->flush(true), FILE_APPEND);
235
	}
236
237
	public function isCData($name, $customColumns = [])
238
	{
239
		if ($customColumns) {
240
			return \array_key_exists($name, $customColumns);
241
		}
242
		if (($fieldModel = $this->moduleFieldInstances[$name] ?? false) && \in_array($fieldModel->getFieldDataType(), ['text', 'multiEmail'])) {
243
			return true;
244
		}
245
		return false;
246
	}
247
248
	public function createXmlFromTemplate($entries, $entriesInventory)
0 ignored issues
show
Unused Code introduced by
The parameter $entries is not used and could be removed. ( Ignorable by Annotation )

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

248
	public function createXmlFromTemplate(/** @scrutinizer ignore-unused */ $entries, $entriesInventory)

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

Loading history...
Unused Code introduced by
The parameter $entriesInventory is not used and could be removed. ( Ignorable by Annotation )

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

248
	public function createXmlFromTemplate($entries, /** @scrutinizer ignore-unused */ $entriesInventory)

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

Loading history...
249
	{
250
	}
251
}
252