Merge pull request #4861 from colemanw/CRM-15789
[civicrm-core.git] / CRM / Import / Parser.php
CommitLineData
ec3811b1
CW
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
ec3811b1 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
ec3811b1
CW
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
ec3811b1
CW
32 * $Id$
33 *
34 */
35
36
37abstract class CRM_Import_Parser {
38 /**
39 * Settings
40 */
7da04cde 41 const MAX_ERRORS = 250, MAX_WARNINGS = 25, DEFAULT_TIMEOUT = 30;
ec3811b1
CW
42
43 /**
44 * Return codes
45 */
7da04cde 46 const VALID = 1, WARNING = 2, ERROR = 4, CONFLICT = 8, STOP = 16, DUPLICATE = 32, MULTIPLE_DUPE = 64, NO_MATCH = 128, UNPARSED_ADDRESS_WARNING = 256;
ec3811b1
CW
47
48 /**
49 * Parser modes
50 */
7da04cde 51 const MODE_MAPFIELD = 1, MODE_PREVIEW = 2, MODE_SUMMARY = 4, MODE_IMPORT = 8;
ec3811b1
CW
52
53 /**
54 * Codes for duplicate record handling
55 */
7da04cde 56 const DUPLICATE_SKIP = 1, DUPLICATE_REPLACE = 2, DUPLICATE_UPDATE = 4, DUPLICATE_FILL = 8, DUPLICATE_NOCHECK = 16;
ec3811b1
CW
57
58 /**
59 * Contact types
60 */
7da04cde 61 const CONTACT_INDIVIDUAL = 1, CONTACT_HOUSEHOLD = 2, CONTACT_ORGANIZATION = 4;
69a4c20a
CW
62
63
64 /**
100fef9d 65 * Total number of non empty lines
69a4c20a
CW
66 */
67 protected $_totalCount;
68
69 /**
100fef9d 70 * Running total number of valid lines
69a4c20a
CW
71 */
72 protected $_validCount;
73
74 /**
100fef9d 75 * Running total number of invalid rows
69a4c20a
CW
76 */
77 protected $_invalidRowCount;
78
79 /**
100fef9d 80 * Maximum number of non-empty/comment lines to process
69a4c20a
CW
81 *
82 * @var int
83 */
84 protected $_maxLinesToProcess;
85
86 /**
100fef9d 87 * Maximum number of invalid rows to store
69a4c20a
CW
88 */
89 protected $_maxErrorCount;
90
91 /**
100fef9d 92 * Array of error lines, bounded by MAX_ERROR
69a4c20a
CW
93 */
94 protected $_errors;
95
96 /**
100fef9d 97 * Total number of conflict lines
69a4c20a
CW
98 */
99 protected $_conflictCount;
100
101 /**
100fef9d 102 * Array of conflict lines
69a4c20a
CW
103 */
104 protected $_conflicts;
105
106 /**
100fef9d 107 * Total number of duplicate (from database) lines
69a4c20a
CW
108 */
109 protected $_duplicateCount;
110
111 /**
100fef9d 112 * Array of duplicate lines
69a4c20a
CW
113 */
114 protected $_duplicates;
115
116 /**
100fef9d 117 * Running total number of warnings
69a4c20a
CW
118 */
119 protected $_warningCount;
120
121 /**
100fef9d 122 * Maximum number of warnings to store
69a4c20a
CW
123 */
124 protected $_maxWarningCount = self::MAX_WARNINGS;
125
126 /**
100fef9d 127 * Array of warning lines, bounded by MAX_WARNING
69a4c20a
CW
128 */
129 protected $_warnings;
130
131 /**
100fef9d 132 * Array of all the fields that could potentially be part
69a4c20a
CW
133 * of this import process
134 * @var array
135 */
136 protected $_fields;
137
138 /**
100fef9d 139 * Array of the fields that are actually part of the import process
69a4c20a
CW
140 * the position in the array also dictates their position in the import
141 * file
142 * @var array
143 */
144 protected $_activeFields;
145
146 /**
100fef9d 147 * Cache the count of active fields
69a4c20a
CW
148 *
149 * @var int
150 */
151 protected $_activeFieldCount;
152
153 /**
100fef9d 154 * Cache of preview rows
69a4c20a
CW
155 *
156 * @var array
157 */
158 protected $_rows;
159
160 /**
100fef9d 161 * Filename of error data
69a4c20a
CW
162 *
163 * @var string
164 */
165 protected $_errorFileName;
166
167 /**
100fef9d 168 * Filename of conflict data
69a4c20a
CW
169 *
170 * @var string
171 */
172 protected $_conflictFileName;
173
174 /**
100fef9d 175 * Filename of duplicate data
69a4c20a
CW
176 *
177 * @var string
178 */
179 protected $_duplicateFileName;
180
181 /**
100fef9d 182 * Contact type
69a4c20a
CW
183 *
184 * @var int
185 */
186 public $_contactType;
187
188 /**
189 * Class constructor
190 */
00be9182 191 public function __construct() {
69a4c20a
CW
192 $this->_maxLinesToProcess = 0;
193 $this->_maxErrorCount = self::MAX_ERRORS;
194 }
195
196 /**
197 * Abstract function definitions
198 */
199 abstract function init();
e0ef6999
EM
200
201 /**
202 * @return mixed
203 */
69a4c20a 204 abstract function fini();
e0ef6999
EM
205
206 /**
207 * @param $values
208 *
209 * @return mixed
210 */
69a4c20a 211 abstract function mapField(&$values);
e0ef6999
EM
212
213 /**
214 * @param $values
215 *
216 * @return mixed
217 */
69a4c20a 218 abstract function preview(&$values);
e0ef6999
EM
219
220 /**
221 * @param $values
222 *
223 * @return mixed
224 */
69a4c20a 225 abstract function summary(&$values);
e0ef6999
EM
226
227 /**
228 * @param $onDuplicate
229 * @param $values
230 *
231 * @return mixed
232 */
69a4c20a
CW
233 abstract function import($onDuplicate, &$values);
234
235 /**
236 * Set and validate field values
237 *
6f69cc11
TO
238 * @param $elements
239 * : array.
240 * @param $erroneousField
241 * : reference.
77b97be7
EM
242 *
243 * @return int
69a4c20a 244 */
00be9182 245 public function setActiveFieldValues($elements, &$erroneousField) {
69a4c20a
CW
246 $maxCount = count($elements) < $this->_activeFieldCount ? count($elements) : $this->_activeFieldCount;
247 for ($i = 0; $i < $maxCount; $i++) {
248 $this->_activeFields[$i]->setValue($elements[$i]);
249 }
250
251 // reset all the values that we did not have an equivalent import element
252 for (; $i < $this->_activeFieldCount; $i++) {
253 $this->_activeFields[$i]->resetValue();
254 }
255
256 // now validate the fields and return false if error
257 $valid = self::VALID;
258 for ($i = 0; $i < $this->_activeFieldCount; $i++) {
259 if (!$this->_activeFields[$i]->validate()) {
260 // no need to do any more validation
261 $erroneousField = $i;
262 $valid = self::ERROR;
263 break;
264 }
265 }
266 return $valid;
267 }
268
269 /**
270 * Format the field values for input to the api
271 *
272 * @return array (reference) associative array of name/value pairs
69a4c20a 273 */
00be9182 274 public function &getActiveFieldParams() {
69a4c20a
CW
275 $params = array();
276 for ($i = 0; $i < $this->_activeFieldCount; $i++) {
277 if (isset($this->_activeFields[$i]->_value)
278 && !isset($params[$this->_activeFields[$i]->_name])
279 && !isset($this->_activeFields[$i]->_related)
280 ) {
281
282 $params[$this->_activeFields[$i]->_name] = $this->_activeFields[$i]->_value;
283 }
284 }
285 return $params;
286 }
287
e0ef6999
EM
288 /**
289 * @return array
290 */
00be9182 291 public function getSelectValues() {
69a4c20a
CW
292 $values = array();
293 foreach ($this->_fields as $name => $field) {
294 $values[$name] = $field->_title;
295 }
296 return $values;
297 }
298
e0ef6999
EM
299 /**
300 * @return array
301 */
00be9182 302 public function getSelectTypes() {
69a4c20a
CW
303 $values = array();
304 foreach ($this->_fields as $name => $field) {
305 if (isset($field->_hasLocationType)) {
306 $values[$name] = $field->_hasLocationType;
307 }
308 }
309 return $values;
310 }
311
e0ef6999
EM
312 /**
313 * @return array
314 */
00be9182 315 public function getHeaderPatterns() {
69a4c20a
CW
316 $values = array();
317 foreach ($this->_fields as $name => $field) {
318 if (isset($field->_headerPattern)) {
319 $values[$name] = $field->_headerPattern;
320 }
321 }
322 return $values;
323 }
324
e0ef6999
EM
325 /**
326 * @return array
327 */
00be9182 328 public function getDataPatterns() {
69a4c20a
CW
329 $values = array();
330 foreach ($this->_fields as $name => $field) {
331 $values[$name] = $field->_dataPattern;
332 }
333 return $values;
334 }
335
336 /**
337 * Remove single-quote enclosures from a value array (row)
338 *
339 * @param array $values
340 * @param string $enclosure
341 *
342 * @return void
343 * @static
69a4c20a 344 */
00be9182 345 public static function encloseScrub(&$values, $enclosure = "'") {
69a4c20a
CW
346 if (empty($values)) {
347 return;
348 }
349
350 foreach ($values as $k => $v) {
351 $values[$k] = preg_replace("/^$enclosure(.*)$enclosure$/", '$1', $v);
352 }
353 }
354
355 /**
100fef9d 356 * Setter function
69a4c20a
CW
357 *
358 * @param int $max
359 *
360 * @return void
69a4c20a 361 */
00be9182 362 public function setMaxLinesToProcess($max) {
69a4c20a
CW
363 $this->_maxLinesToProcess = $max;
364 }
365
366 /**
367 * Determines the file extension based on error code
368 *
369 * @var $type error code constant
370 * @return string
371 * @static
372 */
00be9182 373 public static function errorFileName($type) {
69a4c20a
CW
374 $fileName = NULL;
375 if (empty($type)) {
376 return $fileName;
377 }
378
379 $config = CRM_Core_Config::singleton();
380 $fileName = $config->uploadDir . "sqlImport";
381 switch ($type) {
382 case self::ERROR:
383 $fileName .= '.errors';
384 break;
385
386 case self::CONFLICT:
387 $fileName .= '.conflicts';
388 break;
389
390 case self::DUPLICATE:
391 $fileName .= '.duplicates';
392 break;
393
394 case self::NO_MATCH:
395 $fileName .= '.mismatch';
396 break;
397
398 case self::UNPARSED_ADDRESS_WARNING:
399 $fileName .= '.unparsedAddress';
400 break;
401 }
402
403 return $fileName;
404 }
405
406 /**
407 * Determines the file name based on error code
408 *
409 * @var $type error code constant
410 * @return string
411 * @static
412 */
00be9182 413 public static function saveFileName($type) {
69a4c20a
CW
414 $fileName = NULL;
415 if (empty($type)) {
416 return $fileName;
417 }
418 switch ($type) {
419 case self::ERROR:
420 $fileName = 'Import_Errors.csv';
421 break;
422
423 case self::CONFLICT:
424 $fileName = 'Import_Conflicts.csv';
425 break;
426
427 case self::DUPLICATE:
428 $fileName = 'Import_Duplicates.csv';
429 break;
430
431 case self::NO_MATCH:
432 $fileName = 'Import_Mismatch.csv';
433 break;
434
435 case self::UNPARSED_ADDRESS_WARNING:
436 $fileName = 'Import_Unparsed_Address.csv';
437 break;
438 }
439
440 return $fileName;
441 }
442
ec3811b1 443}