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