Create parent for parser classes CRM-11254
[civicrm-core.git] / CRM / Contact / Import / Parser.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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-2013
32 * $Id$
33 *
34 */
35
36
37 abstract class CRM_Contact_Import_Parser extends CRM_Import_Parser {
38
39 protected $_tableName;
40
41 /**#@+
42 * @access protected
43 * @var integer
44 */
45
46 /**
47 * total number of lines in file
48 */
49 protected $_rowCount;
50
51 /**
52 * total number of non empty lines
53 */
54 protected $_totalCount;
55
56 /**
57 * running total number of valid lines
58 */
59 protected $_validCount;
60
61 /**
62 * running total number of invalid rows
63 */
64 protected $_invalidRowCount;
65
66 /**
67 * maximum number of invalid rows to store
68 */
69 protected $_maxErrorCount;
70
71 /**
72 * array of error lines, bounded by MAX_ERROR
73 */
74 protected $_errors;
75
76 /**
77 * total number of conflict lines
78 */
79 protected $_conflictCount;
80
81 /**
82 * array of conflict lines
83 */
84 protected $_conflicts;
85
86 /**
87 * total number of duplicate (from database) lines
88 */
89 protected $_duplicateCount;
90
91 /**
92 * array of duplicate lines
93 */
94 protected $_duplicates;
95
96 /**
97 * running total number of warnings
98 */
99 protected $_warningCount;
100
101 /**
102 * running total number of un matched Conact
103 */
104 protected $_unMatchCount;
105
106 /**
107 * array of unmatched lines
108 */
109 protected $_unMatch;
110
111 /**
112 * maximum number of warnings to store
113 */
114 protected $_maxWarningCount = self::MAX_WARNINGS;
115
116 /**
117 * total number of contacts with unparsed addresses
118 */
119 protected $_unparsedAddressCount;
120
121 /**
122 * array of warning lines, bounded by MAX_WARNING
123 */
124 protected $_warnings;
125
126 /**
127 * array of all the fields that could potentially be part
128 * of this import process
129 * @var array
130 */
131 protected $_fields;
132
133 /**
134 * array of the fields that are actually part of the import process
135 * the position in the array also dictates their position in the import
136 * file
137 * @var array
138 */
139 protected $_activeFields;
140
141 /**
142 * cache the count of active fields
143 *
144 * @var int
145 */
146 protected $_activeFieldCount;
147
148 /**
149 * maximum number of non-empty/comment lines to process
150 *
151 * @var int
152 */
153 protected $_maxLinesToProcess;
154
155 /**
156 * cache of preview rows
157 *
158 * @var array
159 */
160 protected $_rows;
161
162 /**
163 * filename of error data
164 *
165 * @var string
166 */
167 protected $_errorFileName;
168
169 /**
170 * filename of conflict data
171 *
172 * @var string
173 */
174 protected $_conflictFileName;
175
176 /**
177 * filename of duplicate data
178 *
179 * @var string
180 */
181 protected $_duplicateFileName;
182
183 /**
184 * filename of mismatch data
185 *
186 * @var string
187 */
188 protected $_misMatchFilemName;
189
190 protected $_primaryKeyName;
191 protected $_statusFieldName;
192
193 /**
194 * contact type
195 *
196 * @var int
197 */
198
199 public $_contactType;
200
201 /**
202 * on duplicate
203 *
204 * @var int
205 */
206 public $_onDuplicate;
207
208 /**
209 * dedupe rule group id to use if set
210 *
211 * @var int
212 */
213 public $_dedupeRuleGroupID = NULL;
214
215 function __construct() {
216 $this->_maxLinesToProcess = 0;
217 $this->_maxErrorCount = self::MAX_ERRORS;
218 }
219
220 abstract function init();
221
222 function run($tableName,
223 &$mapper,
224 $mode = self::MODE_PREVIEW,
225 $contactType = self::CONTACT_INDIVIDUAL,
226 $primaryKeyName = '_id',
227 $statusFieldName = '_status',
228 $onDuplicate = self::DUPLICATE_SKIP,
229 $statusID = NULL,
230 $totalRowCount = NULL,
231 $doGeocodeAddress = FALSE,
232 $timeout = CRM_Contact_Import_Parser::DEFAULT_TIMEOUT,
233 $contactSubType = NULL,
234 $dedupeRuleGroupID = NULL
235 ) {
236
237 // TODO: Make the timeout actually work
238 $this->_onDuplicate = $onDuplicate;
239 $this->_dedupeRuleGroupID = $dedupeRuleGroupID;
240
241 switch ($contactType) {
242 case CRM_Contact_Import_Parser::CONTACT_INDIVIDUAL:
243 $this->_contactType = 'Individual';
244 break;
245
246 case CRM_Contact_Import_Parser::CONTACT_HOUSEHOLD:
247 $this->_contactType = 'Household';
248 break;
249
250 case CRM_Contact_Import_Parser::CONTACT_ORGANIZATION:
251 $this->_contactType = 'Organization';
252 }
253
254 $this->_contactSubType = $contactSubType;
255
256 $this->init();
257
258 $this->_rowCount = $this->_warningCount = 0;
259 $this->_invalidRowCount = $this->_validCount = 0;
260 $this->_totalCount = $this->_conflictCount = 0;
261
262 $this->_errors = array();
263 $this->_warnings = array();
264 $this->_conflicts = array();
265 $this->_unparsedAddresses = array();
266
267 $status = '';
268
269 $this->_tableName = $tableName;
270 $this->_primaryKeyName = $primaryKeyName;
271 $this->_statusFieldName = $statusFieldName;
272
273 if ($mode == self::MODE_MAPFIELD) {
274 $this->_rows = array();
275 }
276 else {
277 $this->_activeFieldCount = count($this->_activeFields);
278 }
279
280 if ($mode == self::MODE_IMPORT) {
281 //get the key of email field
282 foreach ($mapper as $key => $value) {
283 if (strtolower($value) == 'email') {
284 $emailKey = $key;
285 break;
286 }
287 }
288 }
289
290 if ($statusID) {
291 $skip = 50;
292 // $skip = 1;
293 $config = CRM_Core_Config::singleton();
294 $statusFile = "{$config->uploadDir}status_{$statusID}.txt";
295 $status = "<div class='description'>&nbsp; " . ts('No processing status reported yet.') . "</div>";
296
297 //do not force the browser to display the save dialog, CRM-7640
298 $contents = json_encode(array(0, $status));
299
300 file_put_contents($statusFile, $contents);
301
302 $startTimestamp = $currTimestamp = $prevTimestamp = time();
303 }
304
305 // get the contents of the temp. import table
306 $query = "SELECT * FROM $tableName";
307 if ($mode == self::MODE_IMPORT) {
308 $query .= " WHERE $statusFieldName = 'NEW'";
309 }
310 $dao = new CRM_Core_DAO();
311 $db = $dao->getDatabaseConnection();
312 $result = $db->query($query);
313
314 while ($values = $result->fetchRow(DB_FETCHMODE_ORDERED)) {
315 $this->_rowCount++;
316
317 /* trim whitespace around the values */
318
319 $empty = TRUE;
320 foreach ($values as $k => $v) {
321 $values[$k] = trim($v, " \t\r\n");
322 }
323 if (CRM_Utils_System::isNull($values)) {
324 continue;
325 }
326
327 $this->_totalCount++;
328
329 if ($mode == self::MODE_MAPFIELD) {
330 $returnCode = $this->mapField($values);
331 }
332 elseif ($mode == self::MODE_PREVIEW) {
333 $returnCode = $this->preview($values);
334 }
335 elseif ($mode == self::MODE_SUMMARY) {
336 $returnCode = $this->summary($values);
337 }
338 elseif ($mode == self::MODE_IMPORT) {
339 //print "Running parser in import mode<br/>\n";
340 $returnCode = $this->import($onDuplicate, $values, $doGeocodeAddress);
341 if ($statusID && (($this->_rowCount % $skip) == 0)) {
342 $currTimestamp = time();
343 $totalTime = ($currTimestamp - $startTimestamp);
344 $time = ($currTimestamp - $prevTimestamp);
345 $recordsLeft = $totalRowCount - $this->_rowCount;
346 if ($recordsLeft < 0) {
347 $recordsLeft = 0;
348 }
349 $estimatedTime = ($recordsLeft / $skip) * $time;
350 $estMinutes = floor($estimatedTime / 60);
351 $timeFormatted = '';
352 if ($estMinutes > 1) {
353 $timeFormatted = $estMinutes . ' ' . ts('minutes') . ' ';
354 $estimatedTime = $estimatedTime - ($estMinutes * 60);
355 }
356 $timeFormatted .= round($estimatedTime) . ' ' . ts('seconds');
357 $processedPercent = (int )(($this->_rowCount * 100) / $totalRowCount);
358 $statusMsg = ts('%1 of %2 records - %3 remaining',
359 array(1 => $this->_rowCount, 2 => $totalRowCount, 3 => $timeFormatted)
360 );
361 $status = "
362 <div class=\"description\">
363 &nbsp; <strong>{$statusMsg}</strong>
364 </div>
365 ";
366
367 $contents = json_encode (array($processedPercent, $status));
368
369 file_put_contents($statusFile, $contents);
370
371 $prevTimestamp = $currTimestamp;
372 }
373 // sleep(1);
374 }
375 else {
376 $returnCode = self::ERROR;
377 }
378
379 // note that a line could be valid but still produce a warning
380 if ($returnCode & self::VALID) {
381 $this->_validCount++;
382 if ($mode == self::MODE_MAPFIELD) {
383 $this->_rows[] = $values;
384 $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
385 }
386 }
387
388 if ($returnCode & self::WARNING) {
389 $this->_warningCount++;
390 if ($this->_warningCount < $this->_maxWarningCount) {
391 $this->_warningCount[] = $line;
392 }
393 }
394
395 if ($returnCode & self::ERROR) {
396 $this->_invalidRowCount++;
397 if ($this->_invalidRowCount < $this->_maxErrorCount) {
398 array_unshift($values, $this->_rowCount);
399 $this->_errors[] = $values;
400 }
401 }
402
403 if ($returnCode & self::CONFLICT) {
404 $this->_conflictCount++;
405 array_unshift($values, $this->_rowCount);
406 $this->_conflicts[] = $values;
407 }
408
409 if ($returnCode & self::NO_MATCH) {
410 $this->_unMatchCount++;
411 array_unshift($values, $this->_rowCount);
412 $this->_unMatch[] = $values;
413 }
414
415 if ($returnCode & self::DUPLICATE) {
416 if ($returnCode & self::MULTIPLE_DUPE) {
417 /* TODO: multi-dupes should be counted apart from singles
418 * on non-skip action */
419 }
420 $this->_duplicateCount++;
421 array_unshift($values, $this->_rowCount);
422 $this->_duplicates[] = $values;
423 if ($onDuplicate != self::DUPLICATE_SKIP) {
424 $this->_validCount++;
425 }
426 }
427
428 if ($returnCode & self::UNPARSED_ADDRESS_WARNING) {
429 $this->_unparsedAddressCount++;
430 array_unshift($values, $this->_rowCount);
431 $this->_unparsedAddresses[] = $values;
432 }
433 // we give the derived class a way of aborting the process
434 // note that the return code could be multiple code or'ed together
435 if ($returnCode & self::STOP) {
436 break;
437 }
438
439 // if we are done processing the maxNumber of lines, break
440 if ($this->_maxLinesToProcess > 0 && $this->_validCount >= $this->_maxLinesToProcess) {
441 break;
442 }
443
444 // clean up memory from dao's
445 CRM_Core_DAO::freeResult();
446
447 // see if we've hit our timeout yet
448 /* if ( $the_thing_with_the_stuff ) {
449 do_something( );
450 } */
451 }
452
453
454 if ($mode == self::MODE_PREVIEW || $mode == self::MODE_IMPORT) {
455 $customHeaders = $mapper;
456
457 $customfields = CRM_Core_BAO_CustomField::getFields($this->_contactType);
458 foreach ($customHeaders as $key => $value) {
459 if ($id = CRM_Core_BAO_CustomField::getKeyID($value)) {
460 $customHeaders[$key] = $customfields[$id][0];
461 }
462 }
463
464 if ($this->_invalidRowCount) {
465 // removed view url for invlaid contacts
466 $headers = array_merge(array(ts('Line Number'),
467 ts('Reason'),
468 ),
469 $customHeaders
470 );
471 $this->_errorFileName = self::errorFileName(self::ERROR);
472 self::exportCSV($this->_errorFileName, $headers, $this->_errors);
473 }
474 if ($this->_conflictCount) {
475 $headers = array_merge(array(ts('Line Number'),
476 ts('Reason'),
477 ),
478 $customHeaders
479 );
480 $this->_conflictFileName = self::errorFileName(self::CONFLICT);
481 self::exportCSV($this->_conflictFileName, $headers, $this->_conflicts);
482 }
483 if ($this->_duplicateCount) {
484 $headers = array_merge(array(ts('Line Number'),
485 ts('View Contact URL'),
486 ),
487 $customHeaders
488 );
489
490 $this->_duplicateFileName = self::errorFileName(self::DUPLICATE);
491 self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates);
492 }
493 if ($this->_unMatchCount) {
494 $headers = array_merge(array(ts('Line Number'),
495 ts('Reason'),
496 ),
497 $customHeaders
498 );
499
500 $this->_misMatchFilemName = self::errorFileName(self::NO_MATCH);
501 self::exportCSV($this->_misMatchFilemName, $headers, $this->_unMatch);
502 }
503 if ($this->_unparsedAddressCount) {
504 $headers = array_merge(array(ts('Line Number'),
505 ts('Contact Edit URL'),
506 ),
507 $customHeaders
508 );
509 $this->_errorFileName = self::errorFileName(self::UNPARSED_ADDRESS_WARNING);
510 self::exportCSV($this->_errorFileName, $headers, $this->_unparsedAddresses);
511 }
512 }
513 //echo "$this->_totalCount,$this->_invalidRowCount,$this->_conflictCount,$this->_duplicateCount";
514 return $this->fini();
515 }
516
517 abstract function mapField(&$values);
518 abstract function preview(&$values);
519 abstract function summary(&$values);
520 abstract function import($onDuplicate, &$values);
521
522 abstract function fini();
523
524 /**
525 * Given a list of the importable field keys that the user has selected
526 * set the active fields array to this list
527 *
528 * @param array mapped array of values
529 *
530 * @return void
531 * @access public
532 */
533 function setActiveFields($fieldKeys) {
534 $this->_activeFieldCount = count($fieldKeys);
535 foreach ($fieldKeys as $key) {
536 if (empty($this->_fields[$key])) {
537 $this->_activeFields[] = new CRM_Contact_Import_Field('', ts('- do not import -'));
538 }
539 else {
540 $this->_activeFields[] = clone($this->_fields[$key]);
541 }
542 }
543 }
544
545 function setActiveFieldValues($elements) {
546 $maxCount = count($elements) < $this->_activeFieldCount ? count($elements) : $this->_activeFieldCount;
547 for ($i = 0; $i < $maxCount; $i++) {
548 $this->_activeFields[$i]->setValue($elements[$i]);
549 }
550
551 // reset all the values that we did not have an equivalent import element
552 for (; $i < $this->_activeFieldCount; $i++) {
553 $this->_activeFields[$i]->resetValue();
554 }
555
556 // now validate the fields and return false if error
557 $valid = self::VALID;
558 for ($i = 0; $i < $this->_activeFieldCount; $i++) {
559 if (!$this->_activeFields[$i]->validate()) {
560 // no need to do any more validation
561 $valid = self::ERROR;
562 break;
563 }
564 }
565 return $valid;
566 }
567
568 function setActiveFieldLocationTypes($elements) {
569 for ($i = 0; $i < count($elements); $i++) {
570 $this->_activeFields[$i]->_hasLocationType = $elements[$i];
571 }
572 }
573
574 function setActiveFieldPhoneTypes($elements) {
575 for ($i = 0; $i < count($elements); $i++) {
576 $this->_activeFields[$i]->_phoneType = $elements[$i];
577 }
578 }
579
580 function setActiveFieldWebsiteTypes($elements) {
581 for ($i = 0; $i < count($elements); $i++) {
582 $this->_activeFields[$i]->_websiteType = $elements[$i];
583 }
584 }
585
586 /**
587 * Function to set IM Service Provider type fields
588 *
589 * @param array $elements IM service provider type ids
590 *
591 * @return void
592 * @access public
593 */
594 function setActiveFieldImProviders($elements) {
595 for ($i = 0; $i < count($elements); $i++) {
596 $this->_activeFields[$i]->_imProvider = $elements[$i];
597 }
598 }
599
600 function setActiveFieldRelated($elements) {
601 for ($i = 0; $i < count($elements); $i++) {
602 $this->_activeFields[$i]->_related = $elements[$i];
603 }
604 }
605
606 function setActiveFieldRelatedContactType($elements) {
607 for ($i = 0; $i < count($elements); $i++) {
608 $this->_activeFields[$i]->_relatedContactType = $elements[$i];
609 }
610 }
611
612 function setActiveFieldRelatedContactDetails($elements) {
613 for ($i = 0; $i < count($elements); $i++) {
614 $this->_activeFields[$i]->_relatedContactDetails = $elements[$i];
615 }
616 }
617
618 function setActiveFieldRelatedContactLocType($elements) {
619 for ($i = 0; $i < count($elements); $i++) {
620 $this->_activeFields[$i]->_relatedContactLocType = $elements[$i];
621 }
622 }
623
624 function setActiveFieldRelatedContactPhoneType($elements) {
625 for ($i = 0; $i < count($elements); $i++) {
626 $this->_activeFields[$i]->_relatedContactPhoneType = $elements[$i];
627 }
628 }
629
630 function setActiveFieldRelatedContactWebsiteType($elements) {
631 for ($i = 0; $i < count($elements); $i++) {
632 $this->_activeFields[$i]->_relatedContactWebsiteType = $elements[$i];
633 }
634 }
635
636 /**
637 * Function to set IM Service Provider type fields for related contacts
638 *
639 * @param array $elements IM service provider type ids of related contact
640 *
641 * @return void
642 * @access public
643 */
644 function setActiveFieldRelatedContactImProvider($elements) {
645 for ($i = 0; $i < count($elements); $i++) {
646 $this->_activeFields[$i]->_relatedContactImProvider = $elements[$i];
647 }
648 }
649
650 /**
651 * function to format the field values for input to the api
652 *
653 * @return array (reference ) associative array of name/value pairs
654 * @access public
655 */
656 function &getActiveFieldParams() {
657 $params = array();
658
659 //CRM_Core_Error::debug( 'Count', $this->_activeFieldCount );
660 for ($i = 0; $i < $this->_activeFieldCount; $i++) {
661 if ($this->_activeFields[$i]->_name == 'do_not_import') {
662 continue;
663 }
664
665 if (isset($this->_activeFields[$i]->_value)) {
666 if (isset($this->_activeFields[$i]->_hasLocationType)) {
667 if (!isset($params[$this->_activeFields[$i]->_name])) {
668 $params[$this->_activeFields[$i]->_name] = array();
669 }
670
671 $value = array(
672 $this->_activeFields[$i]->_name =>
673 $this->_activeFields[$i]->_value,
674 'location_type_id' =>
675 $this->_activeFields[$i]->_hasLocationType,
676 );
677
678 if (isset($this->_activeFields[$i]->_phoneType)) {
679 $value['phone_type_id'] = $this->_activeFields[$i]->_phoneType;
680 }
681
682 // get IM service Provider type id
683 if (isset($this->_activeFields[$i]->_imProvider)) {
684 $value['provider_id'] = $this->_activeFields[$i]->_imProvider;
685 }
686
687 $params[$this->_activeFields[$i]->_name][] = $value;
688 }
689 elseif (isset($this->_activeFields[$i]->_websiteType)) {
690 $value = array(
691 $this->_activeFields[$i]->_name => $this->_activeFields[$i]->_value,
692 'website_type_id' => $this->_activeFields[$i]->_websiteType,
693 );
694
695 $params[$this->_activeFields[$i]->_name][] = $value;
696 }
697
698 if (!isset($params[$this->_activeFields[$i]->_name])) {
699 if (!isset($this->_activeFields[$i]->_related)) {
700 $params[$this->_activeFields[$i]->_name] = $this->_activeFields[$i]->_value;
701 }
702 }
703
704 //minor fix for CRM-4062
705 if (isset($this->_activeFields[$i]->_related)) {
706 if (!isset($params[$this->_activeFields[$i]->_related])) {
707 $params[$this->_activeFields[$i]->_related] = array();
708 }
709
710 if (!isset($params[$this->_activeFields[$i]->_related]['contact_type']) && !empty($this->_activeFields[$i]->_relatedContactType)) {
711 $params[$this->_activeFields[$i]->_related]['contact_type'] = $this->_activeFields[$i]->_relatedContactType;
712 }
713
714 if (isset($this->_activeFields[$i]->_relatedContactLocType) && !empty($this->_activeFields[$i]->_value)) {
715 if (!is_array($params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails])) {
716 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails] = array();
717 }
718 $value = array(
719 $this->_activeFields[$i]->_relatedContactDetails => $this->_activeFields[$i]->_value,
720 'location_type_id' => $this->_activeFields[$i]->_relatedContactLocType,
721 );
722
723 if (isset($this->_activeFields[$i]->_relatedContactPhoneType)) {
724 $value['phone_type_id'] = $this->_activeFields[$i]->_relatedContactPhoneType;
725 }
726
727 // get IM service Provider type id for related contact
728 if (isset($this->_activeFields[$i]->_relatedContactImProvider)) {
729 $value['provider_id'] = $this->_activeFields[$i]->_relatedContactImProvider;
730 }
731
732 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails][] = $value;
733 }
734 elseif (isset($this->_activeFields[$i]->_relatedContactWebsiteType)) {
735 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails][] = array(
736 'url' => $this->_activeFields[$i]->_value,
737 'website_type_id' => $this->_activeFields[$i]->_relatedContactWebsiteType,
738 );
739 }
740 else {
741 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails] = $this->_activeFields[$i]->_value;
742 }
743 }
744 }
745 }
746
747 return $params;
748 }
749
750 function getSelectValues() {
751 $values = array();
752 foreach ($this->_fields as $name => $field) {
753 $values[$name] = $field->_title;
754 }
755 return $values;
756 }
757
758 function getSelectTypes() {
759 $values = array();
760 foreach ($this->_fields as $name => $field) {
761 $values[$name] = $field->_hasLocationType;
762 }
763 return $values;
764 }
765
766 function getColumnPatterns() {
767 $values = array();
768 foreach ($this->_fields as $name => $field) {
769 $values[$name] = $field->_columnPattern;
770 }
771 return $values;
772 }
773
774 function getDataPatterns() {
775 $values = array();
776 foreach ($this->_fields as $name => $field) {
777 $values[$name] = $field->_dataPattern;
778 }
779 return $values;
780 }
781
782 function addField($name, $title, $type = CRM_Utils_Type::T_INT,
783 $headerPattern = '//', $dataPattern = '//',
784 $hasLocationType = FALSE
785 ) {
786 $this->_fields[$name] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType);
787 if (empty($name)) {
788 $this->_fields['doNotImport'] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType);
789 }
790 }
791
792 /**
793 * setter function
794 *
795 * @param int $max
796 *
797 * @return void
798 * @access public
799 */
800 function setMaxLinesToProcess($max) {
801 $this->_maxLinesToProcess = $max;
802 }
803
804 /**
805 * Store parser values
806 *
807 * @param CRM_Core_Session $store
808 *
809 * @return void
810 * @access public
811 */
812 function set($store, $mode = self::MODE_SUMMARY) {
813 $store->set('rowCount', $this->_rowCount);
814 $store->set('fields', $this->getSelectValues());
815 $store->set('fieldTypes', $this->getSelectTypes());
816
817 $store->set('columnPatterns', $this->getColumnPatterns());
818 $store->set('dataPatterns', $this->getDataPatterns());
819 $store->set('columnCount', $this->_activeFieldCount);
820
821 $store->set('totalRowCount', $this->_totalCount);
822 $store->set('validRowCount', $this->_validCount);
823 $store->set('invalidRowCount', $this->_invalidRowCount);
824 $store->set('conflictRowCount', $this->_conflictCount);
825 $store->set('unMatchCount', $this->_unMatchCount);
826
827 switch ($this->_contactType) {
828 case 'Individual':
829 $store->set('contactType', CRM_Contact_Import_Parser::CONTACT_INDIVIDUAL);
830 break;
831
832 case 'Household':
833 $store->set('contactType', CRM_Contact_Import_Parser::CONTACT_HOUSEHOLD);
834 break;
835
836 case 'Organization':
837 $store->set('contactType', CRM_Contact_Import_Parser::CONTACT_ORGANIZATION);
838 }
839
840 if ($this->_invalidRowCount) {
841 $store->set('errorsFileName', $this->_errorFileName);
842 }
843 if ($this->_conflictCount) {
844 $store->set('conflictsFileName', $this->_conflictFileName);
845 }
846 if (isset($this->_rows) && !empty($this->_rows)) {
847 $store->set('dataValues', $this->_rows);
848 }
849
850 if ($this->_unMatchCount) {
851 $store->set('mismatchFileName', $this->_misMatchFilemName);
852 }
853
854 if ($mode == self::MODE_IMPORT) {
855 $store->set('duplicateRowCount', $this->_duplicateCount);
856 $store->set('unparsedAddressCount', $this->_unparsedAddressCount);
857 if ($this->_duplicateCount) {
858 $store->set('duplicatesFileName', $this->_duplicateFileName);
859 }
860 if ($this->_unparsedAddressCount) {
861 $store->set('errorsFileName', $this->_errorFileName);
862 }
863 }
864 //echo "$this->_totalCount,$this->_invalidRowCount,$this->_conflictCount,$this->_duplicateCount";
865 }
866
867 /**
868 * Export data to a CSV file
869 *
870 * @param string $filename
871 * @param array $header
872 * @param data $data
873 *
874 * @return void
875 * @access public
876 */
877 static function exportCSV($fileName, $header, $data) {
878
879 if (file_exists($fileName) && !is_writable($fileName)) {
880 CRM_Core_Error::movedSiteError($fileName);
881 }
882 //hack to remove '_status', '_statusMsg' and '_id' from error file
883 $errorValues = array();
884 $dbRecordStatus = array('IMPORTED', 'ERROR', 'DUPLICATE', 'INVALID', 'NEW');
885 foreach ($data as $rowCount => $rowValues) {
886 $count = 0;
887 foreach ($rowValues as $key => $val) {
888 if (in_array($val, $dbRecordStatus) && $count == (count($rowValues) - 3)) {
889 break;
890 }
891 $errorValues[$rowCount][$key] = $val;
892 $count++;
893 }
894 }
895 $data = $errorValues;
896
897 $output = array();
898 $fd = fopen($fileName, 'w');
899
900 foreach ($header as $key => $value) {
901 $header[$key] = "\"$value\"";
902 }
903 $config = CRM_Core_Config::singleton();
904 $output[] = implode($config->fieldSeparator, $header);
905
906 foreach ($data as $datum) {
907 foreach ($datum as $key => $value) {
908 $datum[$key] = "\"$value\"";
909 }
910 $output[] = implode($config->fieldSeparator, $datum);
911 }
912 fwrite($fd, implode("\n", $output));
913 fclose($fd);
914 }
915
916 /**
917 * Update the record with PK $id in the import database table
918 *
919 * @param int $id
920 * @param array $params
921 *
922 * @return void
923 * @access public
924 */
925 public function updateImportRecord($id, &$params) {
926 $statusFieldName = $this->_statusFieldName;
927 $primaryKeyName = $this->_primaryKeyName;
928
929 if ($statusFieldName && $primaryKeyName) {
930 $dao = new CRM_Core_DAO();
931 $db = $dao->getDatabaseConnection();
932
933 $query = "UPDATE $this->_tableName
934 SET $statusFieldName = ?,
935 ${statusFieldName}Msg = ?
936 WHERE $primaryKeyName = ?";
937 $args = array(
938 $params[$statusFieldName],
939 CRM_Utils_Array::value("${statusFieldName}Msg", $params),
940 $id,
941 );
942
943 //print "Running query: $query<br/>With arguments: ".$params[$statusFieldName].", ".$params["${statusFieldName}Msg"].", $id<br/>";
944
945 $db->query($query, $args);
946 }
947 }
948
949 function errorFileName($type) {
950 $fileName = NULL;
951 if (empty($type)) {
952 return $fileName;
953 }
954
955 $config = CRM_Core_Config::singleton();
956 $fileName = $config->uploadDir . "sqlImport";
957 switch ($type) {
958 case CRM_Contact_Import_Parser::ERROR:
959 $fileName .= '.errors';
960 break;
961
962 case CRM_Contact_Import_Parser::CONFLICT:
963 $fileName .= '.conflicts';
964 break;
965
966 case CRM_Contact_Import_Parser::DUPLICATE:
967 $fileName .= '.duplicates';
968 break;
969
970 case CRM_Contact_Import_Parser::NO_MATCH:
971 $fileName .= '.mismatch';
972 break;
973
974 case CRM_Contact_Import_Parser::UNPARSED_ADDRESS_WARNING:
975 $fileName .= '.unparsedAddress';
976 break;
977 }
978
979 return $fileName;
980 }
981
982 function saveFileName($type) {
983 $fileName = NULL;
984 if (empty($type)) {
985 return $fileName;
986 }
987 switch ($type) {
988 case CRM_Contact_Import_Parser::ERROR:
989 $fileName = 'Import_Errors.csv';
990 break;
991
992 case CRM_Contact_Import_Parser::CONFLICT:
993 $fileName = 'Import_Conflicts.csv';
994 break;
995
996 case CRM_Contact_Import_Parser::DUPLICATE:
997 $fileName = 'Import_Duplicates.csv';
998 break;
999
1000 case CRM_Contact_Import_Parser::NO_MATCH:
1001 $fileName = 'Import_Mismatch.csv';
1002 break;
1003
1004 case CRM_Contact_Import_Parser::UNPARSED_ADDRESS_WARNING:
1005 $fileName = 'Import_Unparsed_Address.csv';
1006 break;
1007 }
1008
1009 return $fileName;
1010 }
1011 }
1012