Merge in 5.31
[civicrm-core.git] / CRM / Custom / Import / Parser.php
CommitLineData
9ff5f6c0
N
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
9ff5f6c0 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9ff5f6c0 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
9ff5f6c0
N
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
9ff5f6c0 16 */
9ff5f6c0
N
17abstract class CRM_Custom_Import_Parser extends CRM_Contact_Import_Parser {
18
19 protected $_fileName;
20
518fa0ee 21 /**
9f266042 22 * Imported file size.
23 *
518fa0ee 24 * @var int
9ff5f6c0
N
25 */
26 protected $_fileSize;
27
28 /**
100fef9d 29 * Separator being used
518fa0ee 30 * @var string
9ff5f6c0
N
31 */
32 protected $_separator;
33
34 /**
100fef9d 35 * Total number of lines in file
518fa0ee 36 * @var int
9ff5f6c0
N
37 */
38 protected $_lineCount;
39
40 /**
100fef9d 41 * Whether the file has a column header or not
9ff5f6c0 42 *
d51c6add 43 * @var bool
9ff5f6c0
N
44 */
45 protected $_haveColumnHeader;
46
e0ef6999 47 /**
100fef9d 48 * @param string $fileName
e0ef6999
EM
49 * @param string $separator
50 * @param int $mapper
51 * @param bool $skipColumnHeader
52 * @param int|string $mode
53 * @param int|string $contactType
54 * @param int $onDuplicate
55 *
56 * @return mixed
57 * @throws Exception
58 */
389bcebf 59 public function run(
40b55be5 60 $fileName,
9ff5f6c0
N
61 $separator = ',',
62 &$mapper,
63 $skipColumnHeader = FALSE,
64 $mode = self::MODE_PREVIEW,
65 $contactType = self::CONTACT_INDIVIDUAL,
66 $onDuplicate = self::DUPLICATE_SKIP
67 ) {
68 if (!is_array($fileName)) {
79e11805 69 throw new CRM_Core_Exception('Unable to determine import file');
9ff5f6c0
N
70 }
71 $fileName = $fileName['name'];
72
73 switch ($contactType) {
74 case CRM_Import_Parser::CONTACT_INDIVIDUAL:
75 $this->_contactType = 'Individual';
76 break;
77
78 case CRM_Import_Parser::CONTACT_HOUSEHOLD:
79 $this->_contactType = 'Household';
80 break;
81
82 case CRM_Import_Parser::CONTACT_ORGANIZATION:
83 $this->_contactType = 'Organization';
84 }
85 $this->init();
86
87 $this->_haveColumnHeader = $skipColumnHeader;
88
89 $this->_separator = $separator;
90
91 $fd = fopen($fileName, "r");
92 if (!$fd) {
93 return FALSE;
94 }
95
96 $this->_lineCount = $this->_warningCount = 0;
97 $this->_invalidRowCount = $this->_validCount = 0;
98 $this->_totalCount = $this->_conflictCount = 0;
99
be2fb01f
CW
100 $this->_errors = [];
101 $this->_warnings = [];
102 $this->_conflicts = [];
9ff5f6c0
N
103
104 $this->_fileSize = number_format(filesize($fileName) / 1024.0, 2);
105
106 if ($mode == self::MODE_MAPFIELD) {
be2fb01f 107 $this->_rows = [];
9ff5f6c0
N
108 }
109 else {
110 $this->_activeFieldCount = count($this->_activeFields);
111 }
112
113 while (!feof($fd)) {
114 $this->_lineCount++;
115
116 $values = fgetcsv($fd, 8192, $separator);
117 if (!$values) {
118 continue;
119 }
120
121 self::encloseScrub($values);
122
123 // skip column header if we're not in mapfield mode
124 if ($mode != self::MODE_MAPFIELD && $skipColumnHeader) {
125 $skipColumnHeader = FALSE;
126 continue;
127 }
128
129 /* trim whitespace around the values */
130
131 $empty = TRUE;
132 foreach ($values as $k => $v) {
133 $values[$k] = trim($v, " \t\r\n");
134 }
135
136 if (CRM_Utils_System::isNull($values)) {
137 continue;
138 }
139
140 $this->_totalCount++;
141
142 if ($mode == self::MODE_MAPFIELD) {
143 $returnCode = $this->mapField($values);
144 }
145 elseif ($mode == self::MODE_PREVIEW) {
146 $returnCode = $this->preview($values);
147 }
148 elseif ($mode == self::MODE_SUMMARY) {
149 $returnCode = $this->summary($values);
150 }
151 elseif ($mode == self::MODE_IMPORT) {
152 $returnCode = $this->import($onDuplicate, $values);
153 }
154 else {
155 $returnCode = self::ERROR;
156 }
157
158 // note that a line could be valid but still produce a warning
159 if ($returnCode & self::VALID) {
160 $this->_validCount++;
161 if ($mode == self::MODE_MAPFIELD) {
162 $this->_rows[] = $values;
163 $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
164 }
165 }
166
167 if ($returnCode & self::WARNING) {
168 $this->_warningCount++;
169 if ($this->_warningCount < $this->_maxWarningCount) {
170 $this->_warningCount[] = $line;
171 }
172 }
173
174 if ($returnCode & self::ERROR) {
175 $this->_invalidRowCount++;
ca2057ea
SM
176 $recordNumber = $this->_lineCount;
177 if ($this->_haveColumnHeader) {
178 $recordNumber--;
9ff5f6c0 179 }
ca2057ea
SM
180 array_unshift($values, $recordNumber);
181 $this->_errors[] = $values;
9ff5f6c0
N
182 }
183
184 if ($returnCode & self::CONFLICT) {
185 $this->_conflictCount++;
186 $recordNumber = $this->_lineCount;
187 if ($this->_haveColumnHeader) {
188 $recordNumber--;
189 }
190 array_unshift($values, $recordNumber);
191 $this->_conflicts[] = $values;
192 }
193
194 if ($returnCode & self::DUPLICATE) {
195 if ($returnCode & self::MULTIPLE_DUPE) {
196 /* TODO: multi-dupes should be counted apart from singles
e70a7fc0 197 * on non-skip action */
9ff5f6c0
N
198 }
199 $this->_duplicateCount++;
200 $recordNumber = $this->_lineCount;
201 if ($this->_haveColumnHeader) {
202 $recordNumber--;
203 }
204 array_unshift($values, $recordNumber);
205 $this->_duplicates[] = $values;
206 if ($onDuplicate != self::DUPLICATE_SKIP) {
207 $this->_validCount++;
208 }
209 }
210
211 // we give the derived class a way of aborting the process
212 // note that the return code could be multiple code or'ed together
213 if ($returnCode & self::STOP) {
214 break;
215 }
216
217 // if we are done processing the maxNumber of lines, break
218 if ($this->_maxLinesToProcess > 0 && $this->_validCount >= $this->_maxLinesToProcess) {
219 break;
220 }
221 }
222
223 fclose($fd);
224
9ff5f6c0
N
225 if ($mode == self::MODE_PREVIEW || $mode == self::MODE_IMPORT) {
226 $customHeaders = $mapper;
227
228 $customfields = CRM_Core_BAO_CustomField::getFields('Activity');
229 foreach ($customHeaders as $key => $value) {
230 if ($id = CRM_Core_BAO_CustomField::getKeyID($value)) {
231 $customHeaders[$key] = $customfields[$id][0];
232 }
233 }
234 if ($this->_invalidRowCount) {
235 // removed view url for invlaid contacts
be2fb01f 236 $headers = array_merge([
518fa0ee
SL
237 ts('Line Number'),
238 ts('Reason'),
239 ], $customHeaders);
9ff5f6c0
N
240 $this->_errorFileName = self::errorFileName(self::ERROR);
241 self::exportCSV($this->_errorFileName, $headers, $this->_errors);
242 }
243 if ($this->_conflictCount) {
be2fb01f 244 $headers = array_merge([
518fa0ee
SL
245 ts('Line Number'),
246 ts('Reason'),
247 ], $customHeaders);
9ff5f6c0
N
248 $this->_conflictFileName = self::errorFileName(self::CONFLICT);
249 self::exportCSV($this->_conflictFileName, $headers, $this->_conflicts);
250 }
251 if ($this->_duplicateCount) {
be2fb01f 252 $headers = array_merge([
518fa0ee
SL
253 ts('Line Number'),
254 ts('View Activity History URL'),
255 ], $customHeaders);
9ff5f6c0
N
256
257 $this->_duplicateFileName = self::errorFileName(self::DUPLICATE);
258 self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates);
259 }
260 }
261 return $this->fini();
262 }
263
264 /**
265 * Given a list of the importable field keys that the user has selected
266 * set the active fields array to this list
267 *
389bcebf 268 * @param array $fieldKeys mapped array of values
9ff5f6c0
N
269 *
270 * @return void
9ff5f6c0 271 */
00be9182 272 public function setActiveFields($fieldKeys) {
9ff5f6c0
N
273 $this->_activeFieldCount = count($fieldKeys);
274 foreach ($fieldKeys as $key) {
275 if (empty($this->_fields[$key])) {
276 $this->_activeFields[] = new CRM_Custom_Import_Field('', ts('- do not import -'));
277 }
278 else {
279 $this->_activeFields[] = clone($this->_fields[$key]);
280 }
281 }
282 }
283
284 /**
fe482240 285 * Format the field values for input to the api.
9ff5f6c0 286 *
a6c01b45
CW
287 * @return array
288 * (reference ) associative array of name/value pairs
9ff5f6c0 289 */
00be9182 290 public function &getActiveFieldParams() {
be2fb01f 291 $params = [];
9ff5f6c0
N
292 for ($i = 0; $i < $this->_activeFieldCount; $i++) {
293 if (isset($this->_activeFields[$i]->_value)
294 && !isset($params[$this->_activeFields[$i]->_name])
295 && !isset($this->_activeFields[$i]->_related)
296 ) {
297 $params[$this->_activeFields[$i]->_name] = $this->_activeFields[$i]->_value;
298 }
299 }
300 return $params;
301 }
302
303 /**
fe482240 304 * Store parser values.
9ff5f6c0
N
305 *
306 * @param CRM_Core_Session $store
307 *
da6b46f4
EM
308 * @param int $mode
309 *
9ff5f6c0 310 * @return void
9ff5f6c0 311 */
00be9182 312 public function set($store, $mode = self::MODE_SUMMARY) {
9ff5f6c0
N
313 $store->set('fileSize', $this->_fileSize);
314 $store->set('lineCount', $this->_lineCount);
f0fed404 315 $store->set('separator', $this->_separator);
9ff5f6c0
N
316 $store->set('fields', $this->getSelectValues());
317 $store->set('fieldTypes', $this->getSelectTypes());
318
319 $store->set('headerPatterns', $this->getHeaderPatterns());
320 $store->set('dataPatterns', $this->getDataPatterns());
321 $store->set('columnCount', $this->_activeFieldCount);
322 $store->set('_entity', $this->_entity);
323 $store->set('totalRowCount', $this->_totalCount);
324 $store->set('validRowCount', $this->_validCount);
325 $store->set('invalidRowCount', $this->_invalidRowCount);
326 $store->set('conflictRowCount', $this->_conflictCount);
327
328 switch ($this->_contactType) {
329 case 'Individual':
330 $store->set('contactType', CRM_Import_Parser::CONTACT_INDIVIDUAL);
331 break;
332
333 case 'Household':
334 $store->set('contactType', CRM_Import_Parser::CONTACT_HOUSEHOLD);
335 break;
336
337 case 'Organization':
338 $store->set('contactType', CRM_Import_Parser::CONTACT_ORGANIZATION);
339 }
340
341 if ($this->_invalidRowCount) {
342 $store->set('errorsFileName', $this->_errorFileName);
343 }
344 if ($this->_conflictCount) {
345 $store->set('conflictsFileName', $this->_conflictFileName);
346 }
347 if (isset($this->_rows) && !empty($this->_rows)) {
348 $store->set('dataValues', $this->_rows);
349 }
350
351 if ($mode == self::MODE_IMPORT) {
352 $store->set('duplicateRowCount', $this->_duplicateCount);
353 if ($this->_duplicateCount) {
354 $store->set('duplicatesFileName', $this->_duplicateFileName);
355 }
356 }
357 }
96025800 358
9ff5f6c0 359}