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