CRM-14343 - Remove 250 limit for import errors
[civicrm-core.git] / CRM / Contact / Import / Parser.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
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-2017
32 */
33 abstract class CRM_Contact_Import_Parser extends CRM_Import_Parser {
34
35 protected $_tableName;
36
37 /**
38 * Total number of lines in file
39 *
40 * @var integer
41 */
42 protected $_rowCount;
43
44 /**
45 * Running total number of un-matched Contacts.
46 */
47 protected $_unMatchCount;
48
49 /**
50 * Array of unmatched lines
51 */
52 protected $_unMatch;
53
54 /**
55 * Total number of contacts with unparsed addresses
56 */
57 protected $_unparsedAddressCount;
58
59 /**
60 * Filename of mismatch data
61 *
62 * @var string
63 */
64 protected $_misMatchFilemName;
65
66 protected $_primaryKeyName;
67 protected $_statusFieldName;
68
69 /**
70 * On duplicate
71 *
72 * @var int
73 */
74 public $_onDuplicate;
75
76 /**
77 * Dedupe rule group id to use if set
78 *
79 * @var int
80 */
81 public $_dedupeRuleGroupID = NULL;
82
83 /**
84 * Run import.
85 *
86 * @param string $tableName
87 * @param array $mapper
88 * @param int $mode
89 * @param int $contactType
90 * @param string $primaryKeyName
91 * @param string $statusFieldName
92 * @param int $onDuplicate
93 * @param int $statusID
94 * @param int $totalRowCount
95 * @param bool $doGeocodeAddress
96 * @param int $timeout
97 * @param string $contactSubType
98 * @param int $dedupeRuleGroupID
99 *
100 * @return mixed
101 */
102 public function run(
103 $tableName,
104 &$mapper,
105 $mode = self::MODE_PREVIEW,
106 $contactType = self::CONTACT_INDIVIDUAL,
107 $primaryKeyName = '_id',
108 $statusFieldName = '_status',
109 $onDuplicate = self::DUPLICATE_SKIP,
110 $statusID = NULL,
111 $totalRowCount = NULL,
112 $doGeocodeAddress = FALSE,
113 $timeout = CRM_Contact_Import_Parser::DEFAULT_TIMEOUT,
114 $contactSubType = NULL,
115 $dedupeRuleGroupID = NULL
116 ) {
117
118 // TODO: Make the timeout actually work
119 $this->_onDuplicate = $onDuplicate;
120 $this->_dedupeRuleGroupID = $dedupeRuleGroupID;
121
122 switch ($contactType) {
123 case CRM_Import_Parser::CONTACT_INDIVIDUAL:
124 $this->_contactType = 'Individual';
125 break;
126
127 case CRM_Import_Parser::CONTACT_HOUSEHOLD:
128 $this->_contactType = 'Household';
129 break;
130
131 case CRM_Import_Parser::CONTACT_ORGANIZATION:
132 $this->_contactType = 'Organization';
133 }
134
135 $this->_contactSubType = $contactSubType;
136
137 $this->init();
138
139 $this->_rowCount = $this->_warningCount = 0;
140 $this->_invalidRowCount = $this->_validCount = 0;
141 $this->_totalCount = $this->_conflictCount = 0;
142
143 $this->_errors = array();
144 $this->_warnings = array();
145 $this->_conflicts = array();
146 $this->_unparsedAddresses = array();
147
148 $this->_tableName = $tableName;
149 $this->_primaryKeyName = $primaryKeyName;
150 $this->_statusFieldName = $statusFieldName;
151
152 if ($mode == self::MODE_MAPFIELD) {
153 $this->_rows = array();
154 }
155 else {
156 $this->_activeFieldCount = count($this->_activeFields);
157 }
158
159 if ($mode == self::MODE_IMPORT) {
160 //get the key of email field
161 foreach ($mapper as $key => $value) {
162 if (strtolower($value) == 'email') {
163 $emailKey = $key;
164 break;
165 }
166 }
167 }
168
169 if ($statusID) {
170 $skip = 50;
171 // $skip = 1;
172 $config = CRM_Core_Config::singleton();
173 $statusFile = "{$config->uploadDir}status_{$statusID}.txt";
174 $status = "<div class='description'>&nbsp; " . ts('No processing status reported yet.') . "</div>";
175
176 //do not force the browser to display the save dialog, CRM-7640
177 $contents = json_encode(array(0, $status));
178
179 file_put_contents($statusFile, $contents);
180
181 $startTimestamp = $currTimestamp = $prevTimestamp = time();
182 }
183
184 // get the contents of the temp. import table
185 $query = "SELECT * FROM $tableName";
186 if ($mode == self::MODE_IMPORT) {
187 $query .= " WHERE $statusFieldName = 'NEW'";
188 }
189 $dao = new CRM_Core_DAO();
190 $db = $dao->getDatabaseConnection();
191 $result = $db->query($query);
192
193 while ($values = $result->fetchRow(DB_FETCHMODE_ORDERED)) {
194 $this->_rowCount++;
195
196 /* trim whitespace around the values */
197 foreach ($values as $k => $v) {
198 $values[$k] = trim($v, " \t\r\n");
199 }
200 if (CRM_Utils_System::isNull($values)) {
201 continue;
202 }
203
204 $this->_totalCount++;
205
206 if ($mode == self::MODE_MAPFIELD) {
207 $returnCode = $this->mapField($values);
208 }
209 elseif ($mode == self::MODE_PREVIEW) {
210 $returnCode = $this->preview($values);
211 }
212 elseif ($mode == self::MODE_SUMMARY) {
213 $returnCode = $this->summary($values);
214 }
215 elseif ($mode == self::MODE_IMPORT) {
216 //print "Running parser in import mode<br/>\n";
217 $returnCode = $this->import($onDuplicate, $values, $doGeocodeAddress);
218 if ($statusID && (($this->_rowCount % $skip) == 0)) {
219 $currTimestamp = time();
220 $totalTime = ($currTimestamp - $startTimestamp);
221 $time = ($currTimestamp - $prevTimestamp);
222 $recordsLeft = $totalRowCount - $this->_rowCount;
223 if ($recordsLeft < 0) {
224 $recordsLeft = 0;
225 }
226 $estimatedTime = ($recordsLeft / $skip) * $time;
227 $estMinutes = floor($estimatedTime / 60);
228 $timeFormatted = '';
229 if ($estMinutes > 1) {
230 $timeFormatted = $estMinutes . ' ' . ts('minutes') . ' ';
231 $estimatedTime = $estimatedTime - ($estMinutes * 60);
232 }
233 $timeFormatted .= round($estimatedTime) . ' ' . ts('seconds');
234 $processedPercent = (int ) (($this->_rowCount * 100) / $totalRowCount);
235 $statusMsg = ts('%1 of %2 records - %3 remaining',
236 array(1 => $this->_rowCount, 2 => $totalRowCount, 3 => $timeFormatted)
237 );
238 $status = "
239 <div class=\"description\">
240 &nbsp; <strong>{$statusMsg}</strong>
241 </div>
242 ";
243
244 $contents = json_encode(array($processedPercent, $status));
245
246 file_put_contents($statusFile, $contents);
247
248 $prevTimestamp = $currTimestamp;
249 }
250 // sleep(1);
251 }
252 else {
253 $returnCode = self::ERROR;
254 }
255
256 // note that a line could be valid but still produce a warning
257 if ($returnCode & self::VALID) {
258 $this->_validCount++;
259 if ($mode == self::MODE_MAPFIELD) {
260 $this->_rows[] = $values;
261 $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
262 }
263 }
264
265 if ($returnCode & self::WARNING) {
266 $this->_warningCount++;
267 if ($this->_warningCount < $this->_maxWarningCount) {
268 $this->_warningCount[] = $line;
269 }
270 }
271
272 if ($returnCode & self::ERROR) {
273 $this->_invalidRowCount++;
274 array_unshift($values, $this->_rowCount);
275 $this->_errors[] = $values;
276 }
277
278 if ($returnCode & self::CONFLICT) {
279 $this->_conflictCount++;
280 array_unshift($values, $this->_rowCount);
281 $this->_conflicts[] = $values;
282 }
283
284 if ($returnCode & self::NO_MATCH) {
285 $this->_unMatchCount++;
286 array_unshift($values, $this->_rowCount);
287 $this->_unMatch[] = $values;
288 }
289
290 if ($returnCode & self::DUPLICATE) {
291 if ($returnCode & self::MULTIPLE_DUPE) {
292 /* TODO: multi-dupes should be counted apart from singles
293 * on non-skip action */
294 }
295 $this->_duplicateCount++;
296 array_unshift($values, $this->_rowCount);
297 $this->_duplicates[] = $values;
298 if ($onDuplicate != self::DUPLICATE_SKIP) {
299 $this->_validCount++;
300 }
301 }
302
303 if ($returnCode & self::UNPARSED_ADDRESS_WARNING) {
304 $this->_unparsedAddressCount++;
305 array_unshift($values, $this->_rowCount);
306 $this->_unparsedAddresses[] = $values;
307 }
308 // we give the derived class a way of aborting the process
309 // note that the return code could be multiple code or'ed together
310 if ($returnCode & self::STOP) {
311 break;
312 }
313
314 // if we are done processing the maxNumber of lines, break
315 if ($this->_maxLinesToProcess > 0 && $this->_validCount >= $this->_maxLinesToProcess) {
316 break;
317 }
318
319 // clean up memory from dao's
320 CRM_Core_DAO::freeResult();
321
322 // see if we've hit our timeout yet
323 /* if ( $the_thing_with_the_stuff ) {
324 do_something( );
325 } */
326 }
327
328 if ($mode == self::MODE_PREVIEW || $mode == self::MODE_IMPORT) {
329 $customHeaders = $mapper;
330
331 $customfields = CRM_Core_BAO_CustomField::getFields($this->_contactType);
332 foreach ($customHeaders as $key => $value) {
333 if ($id = CRM_Core_BAO_CustomField::getKeyID($value)) {
334 $customHeaders[$key] = $customfields[$id][0];
335 }
336 }
337
338 if ($this->_invalidRowCount) {
339 // removed view url for invlaid contacts
340 $headers = array_merge(array(
341 ts('Line Number'),
342 ts('Reason'),
343 ),
344 $customHeaders
345 );
346 $this->_errorFileName = self::errorFileName(self::ERROR);
347 self::exportCSV($this->_errorFileName, $headers, $this->_errors);
348 }
349 if ($this->_conflictCount) {
350 $headers = array_merge(array(
351 ts('Line Number'),
352 ts('Reason'),
353 ),
354 $customHeaders
355 );
356 $this->_conflictFileName = self::errorFileName(self::CONFLICT);
357 self::exportCSV($this->_conflictFileName, $headers, $this->_conflicts);
358 }
359 if ($this->_duplicateCount) {
360 $headers = array_merge(array(
361 ts('Line Number'),
362 ts('View Contact URL'),
363 ),
364 $customHeaders
365 );
366
367 $this->_duplicateFileName = self::errorFileName(self::DUPLICATE);
368 self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates);
369 }
370 if ($this->_unMatchCount) {
371 $headers = array_merge(array(
372 ts('Line Number'),
373 ts('Reason'),
374 ),
375 $customHeaders
376 );
377
378 $this->_misMatchFilemName = self::errorFileName(self::NO_MATCH);
379 self::exportCSV($this->_misMatchFilemName, $headers, $this->_unMatch);
380 }
381 if ($this->_unparsedAddressCount) {
382 $headers = array_merge(array(
383 ts('Line Number'),
384 ts('Contact Edit URL'),
385 ),
386 $customHeaders
387 );
388 $this->_errorFileName = self::errorFileName(self::UNPARSED_ADDRESS_WARNING);
389 self::exportCSV($this->_errorFileName, $headers, $this->_unparsedAddresses);
390 }
391 }
392 //echo "$this->_totalCount,$this->_invalidRowCount,$this->_conflictCount,$this->_duplicateCount";
393 return $this->fini();
394 }
395
396 /**
397 * Given a list of the importable field keys that the user has selected.
398 * set the active fields array to this list
399 *
400 * @param array $fieldKeys
401 * Mapped array of values.
402 */
403 public function setActiveFields($fieldKeys) {
404 $this->_activeFieldCount = count($fieldKeys);
405 foreach ($fieldKeys as $key) {
406 if (empty($this->_fields[$key])) {
407 $this->_activeFields[] = new CRM_Contact_Import_Field('', ts('- do not import -'));
408 }
409 else {
410 $this->_activeFields[] = clone($this->_fields[$key]);
411 }
412 }
413 }
414
415 /**
416 * @param $elements
417 */
418 public function setActiveFieldLocationTypes($elements) {
419 for ($i = 0; $i < count($elements); $i++) {
420 $this->_activeFields[$i]->_hasLocationType = $elements[$i];
421 }
422 }
423
424 /**
425 * @param $elements
426 */
427 /**
428 * @param $elements
429 */
430 public function setActiveFieldPhoneTypes($elements) {
431 for ($i = 0; $i < count($elements); $i++) {
432 $this->_activeFields[$i]->_phoneType = $elements[$i];
433 }
434 }
435
436 /**
437 * @param $elements
438 */
439 public function setActiveFieldWebsiteTypes($elements) {
440 for ($i = 0; $i < count($elements); $i++) {
441 $this->_activeFields[$i]->_websiteType = $elements[$i];
442 }
443 }
444
445 /**
446 * Set IM Service Provider type fields.
447 *
448 * @param array $elements
449 * IM service provider type ids.
450 */
451 public function setActiveFieldImProviders($elements) {
452 for ($i = 0; $i < count($elements); $i++) {
453 $this->_activeFields[$i]->_imProvider = $elements[$i];
454 }
455 }
456
457 /**
458 * @param $elements
459 */
460 public function setActiveFieldRelated($elements) {
461 for ($i = 0; $i < count($elements); $i++) {
462 $this->_activeFields[$i]->_related = $elements[$i];
463 }
464 }
465
466 /**
467 * @param $elements
468 */
469 public function setActiveFieldRelatedContactType($elements) {
470 for ($i = 0; $i < count($elements); $i++) {
471 $this->_activeFields[$i]->_relatedContactType = $elements[$i];
472 }
473 }
474
475 /**
476 * @param $elements
477 */
478 public function setActiveFieldRelatedContactDetails($elements) {
479 for ($i = 0; $i < count($elements); $i++) {
480 $this->_activeFields[$i]->_relatedContactDetails = $elements[$i];
481 }
482 }
483
484 /**
485 * @param $elements
486 */
487 public function setActiveFieldRelatedContactLocType($elements) {
488 for ($i = 0; $i < count($elements); $i++) {
489 $this->_activeFields[$i]->_relatedContactLocType = $elements[$i];
490 }
491 }
492
493 /**
494 * Set active field for related contact's phone type.
495 *
496 * @param array $elements
497 */
498 public function setActiveFieldRelatedContactPhoneType($elements) {
499 for ($i = 0; $i < count($elements); $i++) {
500 $this->_activeFields[$i]->_relatedContactPhoneType = $elements[$i];
501 }
502 }
503
504 /**
505 * @param $elements
506 */
507 public function setActiveFieldRelatedContactWebsiteType($elements) {
508 for ($i = 0; $i < count($elements); $i++) {
509 $this->_activeFields[$i]->_relatedContactWebsiteType = $elements[$i];
510 }
511 }
512
513 /**
514 * Set IM Service Provider type fields for related contacts.
515 *
516 * @param array $elements
517 * IM service provider type ids of related contact.
518 */
519 public function setActiveFieldRelatedContactImProvider($elements) {
520 for ($i = 0; $i < count($elements); $i++) {
521 $this->_activeFields[$i]->_relatedContactImProvider = $elements[$i];
522 }
523 }
524
525 /**
526 * Format the field values for input to the api.
527 *
528 * @return array
529 * (reference ) associative array of name/value pairs
530 */
531 public function &getActiveFieldParams() {
532 $params = array();
533
534 for ($i = 0; $i < $this->_activeFieldCount; $i++) {
535 if ($this->_activeFields[$i]->_name == 'do_not_import') {
536 continue;
537 }
538
539 if (isset($this->_activeFields[$i]->_value)) {
540 if (isset($this->_activeFields[$i]->_hasLocationType)) {
541 if (!isset($params[$this->_activeFields[$i]->_name])) {
542 $params[$this->_activeFields[$i]->_name] = array();
543 }
544
545 $value = array(
546 $this->_activeFields[$i]->_name => $this->_activeFields[$i]->_value,
547 'location_type_id' => $this->_activeFields[$i]->_hasLocationType,
548 );
549
550 if (isset($this->_activeFields[$i]->_phoneType)) {
551 $value['phone_type_id'] = $this->_activeFields[$i]->_phoneType;
552 }
553
554 // get IM service Provider type id
555 if (isset($this->_activeFields[$i]->_imProvider)) {
556 $value['provider_id'] = $this->_activeFields[$i]->_imProvider;
557 }
558
559 $params[$this->_activeFields[$i]->_name][] = $value;
560 }
561 elseif (isset($this->_activeFields[$i]->_websiteType)) {
562 $value = array(
563 $this->_activeFields[$i]->_name => $this->_activeFields[$i]->_value,
564 'website_type_id' => $this->_activeFields[$i]->_websiteType,
565 );
566
567 $params[$this->_activeFields[$i]->_name][] = $value;
568 }
569
570 if (!isset($params[$this->_activeFields[$i]->_name])) {
571 if (!isset($this->_activeFields[$i]->_related)) {
572 $params[$this->_activeFields[$i]->_name] = $this->_activeFields[$i]->_value;
573 }
574 }
575
576 //minor fix for CRM-4062
577 if (isset($this->_activeFields[$i]->_related)) {
578 if (!isset($params[$this->_activeFields[$i]->_related])) {
579 $params[$this->_activeFields[$i]->_related] = array();
580 }
581
582 if (!isset($params[$this->_activeFields[$i]->_related]['contact_type']) && !empty($this->_activeFields[$i]->_relatedContactType)) {
583 $params[$this->_activeFields[$i]->_related]['contact_type'] = $this->_activeFields[$i]->_relatedContactType;
584 }
585
586 if (isset($this->_activeFields[$i]->_relatedContactLocType) && !empty($this->_activeFields[$i]->_value)) {
587 if (!empty($params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails]) &&
588 !is_array($params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails])
589 ) {
590 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails] = array();
591 }
592 $value = array(
593 $this->_activeFields[$i]->_relatedContactDetails => $this->_activeFields[$i]->_value,
594 'location_type_id' => $this->_activeFields[$i]->_relatedContactLocType,
595 );
596
597 if (isset($this->_activeFields[$i]->_relatedContactPhoneType)) {
598 $value['phone_type_id'] = $this->_activeFields[$i]->_relatedContactPhoneType;
599 }
600
601 // get IM service Provider type id for related contact
602 if (isset($this->_activeFields[$i]->_relatedContactImProvider)) {
603 $value['provider_id'] = $this->_activeFields[$i]->_relatedContactImProvider;
604 }
605
606 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails][] = $value;
607 }
608 elseif (isset($this->_activeFields[$i]->_relatedContactWebsiteType)) {
609 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails][] = array(
610 'url' => $this->_activeFields[$i]->_value,
611 'website_type_id' => $this->_activeFields[$i]->_relatedContactWebsiteType,
612 );
613 }
614 else {
615 $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails] = $this->_activeFields[$i]->_value;
616 }
617 }
618 }
619 }
620
621 return $params;
622 }
623
624 /**
625 * @return array
626 */
627 public function getColumnPatterns() {
628 $values = array();
629 foreach ($this->_fields as $name => $field) {
630 $values[$name] = $field->_columnPattern;
631 }
632 return $values;
633 }
634
635 /**
636 * @param string $name
637 * @param $title
638 * @param int $type
639 * @param string $headerPattern
640 * @param string $dataPattern
641 * @param bool $hasLocationType
642 */
643 public function addField(
644 $name, $title, $type = CRM_Utils_Type::T_INT,
645 $headerPattern = '//', $dataPattern = '//',
646 $hasLocationType = FALSE
647 ) {
648 $this->_fields[$name] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType);
649 if (empty($name)) {
650 $this->_fields['doNotImport'] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType);
651 }
652 }
653
654 /**
655 * Store parser values.
656 *
657 * @param CRM_Core_Session $store
658 *
659 * @param int $mode
660 */
661 public function set($store, $mode = self::MODE_SUMMARY) {
662 $store->set('rowCount', $this->_rowCount);
663 $store->set('fields', $this->getSelectValues());
664 $store->set('fieldTypes', $this->getSelectTypes());
665
666 $store->set('columnPatterns', $this->getColumnPatterns());
667 $store->set('dataPatterns', $this->getDataPatterns());
668 $store->set('columnCount', $this->_activeFieldCount);
669
670 $store->set('totalRowCount', $this->_totalCount);
671 $store->set('validRowCount', $this->_validCount);
672 $store->set('invalidRowCount', $this->_invalidRowCount);
673 $store->set('conflictRowCount', $this->_conflictCount);
674 $store->set('unMatchCount', $this->_unMatchCount);
675
676 switch ($this->_contactType) {
677 case 'Individual':
678 $store->set('contactType', CRM_Import_Parser::CONTACT_INDIVIDUAL);
679 break;
680
681 case 'Household':
682 $store->set('contactType', CRM_Import_Parser::CONTACT_HOUSEHOLD);
683 break;
684
685 case 'Organization':
686 $store->set('contactType', CRM_Import_Parser::CONTACT_ORGANIZATION);
687 }
688
689 if ($this->_invalidRowCount) {
690 $store->set('errorsFileName', $this->_errorFileName);
691 }
692 if ($this->_conflictCount) {
693 $store->set('conflictsFileName', $this->_conflictFileName);
694 }
695 if (isset($this->_rows) && !empty($this->_rows)) {
696 $store->set('dataValues', $this->_rows);
697 }
698
699 if ($this->_unMatchCount) {
700 $store->set('mismatchFileName', $this->_misMatchFilemName);
701 }
702
703 if ($mode == self::MODE_IMPORT) {
704 $store->set('duplicateRowCount', $this->_duplicateCount);
705 $store->set('unparsedAddressCount', $this->_unparsedAddressCount);
706 if ($this->_duplicateCount) {
707 $store->set('duplicatesFileName', $this->_duplicateFileName);
708 }
709 if ($this->_unparsedAddressCount) {
710 $store->set('errorsFileName', $this->_errorFileName);
711 }
712 }
713 //echo "$this->_totalCount,$this->_invalidRowCount,$this->_conflictCount,$this->_duplicateCount";
714 }
715
716 /**
717 * Export data to a CSV file.
718 *
719 * @param string $fileName
720 * @param array $header
721 * @param array $data
722 */
723 public static function exportCSV($fileName, $header, $data) {
724
725 if (file_exists($fileName) && !is_writable($fileName)) {
726 CRM_Core_Error::movedSiteError($fileName);
727 }
728 //hack to remove '_status', '_statusMsg' and '_id' from error file
729 $errorValues = array();
730 $dbRecordStatus = array('IMPORTED', 'ERROR', 'DUPLICATE', 'INVALID', 'NEW');
731 foreach ($data as $rowCount => $rowValues) {
732 $count = 0;
733 foreach ($rowValues as $key => $val) {
734 if (in_array($val, $dbRecordStatus) && $count == (count($rowValues) - 3)) {
735 break;
736 }
737 $errorValues[$rowCount][$key] = $val;
738 $count++;
739 }
740 }
741 $data = $errorValues;
742
743 $output = array();
744 $fd = fopen($fileName, 'w');
745
746 foreach ($header as $key => $value) {
747 $header[$key] = "\"$value\"";
748 }
749 $config = CRM_Core_Config::singleton();
750 $output[] = implode($config->fieldSeparator, $header);
751
752 foreach ($data as $datum) {
753 foreach ($datum as $key => $value) {
754 $datum[$key] = "\"$value\"";
755 }
756 $output[] = implode($config->fieldSeparator, $datum);
757 }
758 fwrite($fd, implode("\n", $output));
759 fclose($fd);
760 }
761
762 /**
763 * Update the record with PK $id in the import database table.
764 *
765 * @param int $id
766 * @param array $params
767 */
768 public function updateImportRecord($id, &$params) {
769 $statusFieldName = $this->_statusFieldName;
770 $primaryKeyName = $this->_primaryKeyName;
771
772 if ($statusFieldName && $primaryKeyName) {
773 $dao = new CRM_Core_DAO();
774 $db = $dao->getDatabaseConnection();
775
776 $query = "UPDATE $this->_tableName
777 SET $statusFieldName = ?,
778 ${statusFieldName}Msg = ?
779 WHERE $primaryKeyName = ?";
780 $args = array(
781 $params[$statusFieldName],
782 CRM_Utils_Array::value("${statusFieldName}Msg", $params),
783 $id,
784 );
785
786 //print "Running query: $query<br/>With arguments: ".$params[$statusFieldName].", ".$params["${statusFieldName}Msg"].", $id<br/>";
787
788 $db->query($query, $args);
789 }
790 }
791
792 /**
793 * Format common params data to proper format to store.
794 *
795 * @param array $params
796 * Contain record values.
797 * @param array $formatted
798 * Array of formatted data.
799 * @param array $contactFields
800 * Contact DAO fields.
801 */
802 public function formatCommonData($params, &$formatted, &$contactFields) {
803 $csType = array(
804 CRM_Utils_Array::value('contact_type', $formatted),
805 );
806
807 //CRM-5125
808 //add custom fields for contact sub type
809 if (!empty($this->_contactSubType)) {
810 $csType = $this->_contactSubType;
811 }
812
813 if ($relCsType = CRM_Utils_Array::value('contact_sub_type', $formatted)) {
814 $csType = $relCsType;
815 }
816
817 $customFields = CRM_Core_BAO_CustomField::getFields($formatted['contact_type'], FALSE, FALSE, $csType);
818
819 $addressCustomFields = CRM_Core_BAO_CustomField::getFields('Address');
820 $customFields = $customFields + $addressCustomFields;
821
822 //if a Custom Email Greeting, Custom Postal Greeting or Custom Addressee is mapped, and no "Greeting / Addressee Type ID" is provided, then automatically set the type = Customized, CRM-4575
823 $elements = array(
824 'email_greeting_custom' => 'email_greeting',
825 'postal_greeting_custom' => 'postal_greeting',
826 'addressee_custom' => 'addressee',
827 );
828 foreach ($elements as $k => $v) {
829 if (array_key_exists($k, $params) && !(array_key_exists($v, $params))) {
830 $label = key(CRM_Core_OptionGroup::values($v, TRUE, NULL, NULL, 'AND v.name = "Customized"'));
831 $params[$v] = $label;
832 }
833 }
834
835 //format date first
836 $session = CRM_Core_Session::singleton();
837 $dateType = $session->get("dateTypes");
838 foreach ($params as $key => $val) {
839 $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key);
840 if ($customFieldID &&
841 !array_key_exists($customFieldID, $addressCustomFields)
842 ) {
843 //we should not update Date to null, CRM-4062
844 if ($val && ($customFields[$customFieldID]['data_type'] == 'Date')) {
845 //CRM-21267
846 CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key);
847 }
848 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
849 if (empty($val) && !is_numeric($val) && $this->_onDuplicate == CRM_Import_Parser::DUPLICATE_FILL) {
850 //retain earlier value when Import mode is `Fill`
851 unset($params[$key]);
852 }
853 else {
854 $params[$key] = CRM_Utils_String::strtoboolstr($val);
855 }
856 }
857 }
858
859 if ($key == 'birth_date' && $val) {
860 CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key);
861 }
862 elseif ($key == 'deceased_date' && $val) {
863 CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key);
864 $params['is_deceased'] = 1;
865 }
866 elseif ($key == 'is_deceased' && $val) {
867 $params[$key] = CRM_Utils_String::strtoboolstr($val);
868 }
869 elseif ($key == 'gender') {
870 //CRM-4360
871 $params[$key] = $this->checkGender($val);
872 }
873 }
874
875 //now format custom data.
876 foreach ($params as $key => $field) {
877 if (is_array($field)) {
878 $isAddressCustomField = FALSE;
879 foreach ($field as $value) {
880 $break = FALSE;
881 if (is_array($value)) {
882 foreach ($value as $name => $testForEmpty) {
883 if ($addressCustomFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) {
884 $isAddressCustomField = TRUE;
885 break;
886 }
887 // check if $value does not contain IM provider or phoneType
888 if (($name !== 'phone_type_id' || $name !== 'provider_id') && ($testForEmpty === '' || $testForEmpty == NULL)) {
889 $break = TRUE;
890 break;
891 }
892 }
893 }
894 else {
895 $break = TRUE;
896 }
897
898 if (!$break) {
899 $this->formatContactParameters($value, $formatted);
900 }
901 }
902 if (!$isAddressCustomField) {
903 continue;
904 }
905 }
906
907 $formatValues = array(
908 $key => $field,
909 );
910
911 if (($key !== 'preferred_communication_method') && (array_key_exists($key, $contactFields))) {
912 // due to merging of individual table and
913 // contact table, we need to avoid
914 // preferred_communication_method forcefully
915 $formatValues['contact_type'] = $formatted['contact_type'];
916 }
917
918 if ($key == 'id' && isset($field)) {
919 $formatted[$key] = $field;
920 }
921 $this->formatContactParameters($formatValues, $formatted);
922
923 //Handling Custom Data
924 // note: Address custom fields will be handled separately inside formatContactParameters
925 if (($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) &&
926 array_key_exists($customFieldID, $customFields) &&
927 !array_key_exists($customFieldID, $addressCustomFields)
928 ) {
929
930 $extends = CRM_Utils_Array::value('extends', $customFields[$customFieldID]);
931 $htmlType = CRM_Utils_Array::value('html_type', $customFields[$customFieldID]);
932 switch ($htmlType) {
933 case 'Select':
934 case 'Radio':
935 case 'Autocomplete-Select':
936 if ($customFields[$customFieldID]['data_type'] == 'String' || $customFields[$customFieldID]['data_type'] == 'Int') {
937 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
938 foreach ($customOption as $customValue) {
939 $val = CRM_Utils_Array::value('value', $customValue);
940 $label = CRM_Utils_Array::value('label', $customValue);
941 $label = strtolower($label);
942 $value = strtolower(trim($formatted[$key]));
943 if (($value == $label) || ($value == strtolower($val))) {
944 $params[$key] = $formatted[$key] = $val;
945 }
946 }
947 }
948 break;
949
950 case 'CheckBox':
951 case 'AdvMulti-Select':
952 case 'Multi-Select':
953
954 if (!empty($formatted[$key]) && !empty($params[$key])) {
955 $mulValues = explode(',', $formatted[$key]);
956 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
957 $formatted[$key] = array();
958 $params[$key] = array();
959 foreach ($mulValues as $v1) {
960 foreach ($customOption as $v2) {
961 if ((strtolower($v2['label']) == strtolower(trim($v1))) ||
962 (strtolower($v2['value']) == strtolower(trim($v1)))
963 ) {
964 if ($htmlType == 'CheckBox') {
965 $params[$key][$v2['value']] = $formatted[$key][$v2['value']] = 1;
966 }
967 else {
968 $params[$key][] = $formatted[$key][] = $v2['value'];
969 }
970 }
971 }
972 }
973 }
974 break;
975 }
976 }
977 }
978
979 if (!empty($key) && ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && array_key_exists($customFieldID, $customFields) &&
980 !array_key_exists($customFieldID, $addressCustomFields)
981 ) {
982 // @todo calling api functions directly is not supported
983 _civicrm_api3_custom_format_params($params, $formatted, $extends);
984 }
985
986 // to check if not update mode and unset the fields with empty value.
987 if (!$this->_updateWithId && array_key_exists('custom', $formatted)) {
988 foreach ($formatted['custom'] as $customKey => $customvalue) {
989 if (empty($formatted['custom'][$customKey][-1]['is_required'])) {
990 $formatted['custom'][$customKey][-1]['is_required'] = $customFields[$customKey]['is_required'];
991 }
992 $emptyValue = CRM_Utils_Array::value('value', $customvalue[-1]);
993 if (!isset($emptyValue)) {
994 unset($formatted['custom'][$customKey]);
995 }
996 }
997 }
998
999 // parse street address, CRM-5450
1000 if ($this->_parseStreetAddress) {
1001 if (array_key_exists('address', $formatted) && is_array($formatted['address'])) {
1002 foreach ($formatted['address'] as $instance => & $address) {
1003 $streetAddress = CRM_Utils_Array::value('street_address', $address);
1004 if (empty($streetAddress)) {
1005 continue;
1006 }
1007 // parse address field.
1008 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($streetAddress);
1009
1010 //street address consider to be parsed properly,
1011 //If we get street_name and street_number.
1012 if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) {
1013 $parsedFields = array_fill_keys(array_keys($parsedFields), '');
1014 }
1015
1016 // merge parse address w/ main address block.
1017 $address = array_merge($address, $parsedFields);
1018 }
1019 }
1020 }
1021 }
1022
1023 /**
1024 * Format contact parameters.
1025 *
1026 * @todo this function needs re-writing & re-merging into the main function.
1027 *
1028 * Here be dragons.
1029 *
1030 * @param array $values
1031 * @param array $params
1032 *
1033 * @return bool
1034 */
1035 protected function formatContactParameters(&$values, &$params) {
1036 // Crawl through the possible classes:
1037 // Contact
1038 // Individual
1039 // Household
1040 // Organization
1041 // Location
1042 // Address
1043 // Email
1044 // Phone
1045 // IM
1046 // Note
1047 // Custom
1048
1049 // Cache the various object fields
1050 static $fields = array();
1051
1052 // first add core contact values since for other Civi modules they are not added
1053 $contactFields = CRM_Contact_DAO_Contact::fields();
1054 _civicrm_api3_store_values($contactFields, $values, $params);
1055
1056 if (isset($values['contact_type'])) {
1057 // we're an individual/household/org property
1058
1059 $fields[$values['contact_type']] = CRM_Contact_DAO_Contact::fields();
1060
1061 _civicrm_api3_store_values($fields[$values['contact_type']], $values, $params);
1062 return TRUE;
1063 }
1064
1065 if (isset($values['individual_prefix'])) {
1066 if (!empty($params['prefix_id'])) {
1067 $prefixes = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id');
1068 $params['prefix'] = $prefixes[$params['prefix_id']];
1069 }
1070 else {
1071 $params['prefix'] = $values['individual_prefix'];
1072 }
1073 return TRUE;
1074 }
1075
1076 if (isset($values['individual_suffix'])) {
1077 if (!empty($params['suffix_id'])) {
1078 $suffixes = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id');
1079 $params['suffix'] = $suffixes[$params['suffix_id']];
1080 }
1081 else {
1082 $params['suffix'] = $values['individual_suffix'];
1083 }
1084 return TRUE;
1085 }
1086
1087 // CRM-4575
1088 if (isset($values['email_greeting'])) {
1089 if (!empty($params['email_greeting_id'])) {
1090 $emailGreetingFilter = array(
1091 'contact_type' => CRM_Utils_Array::value('contact_type', $params),
1092 'greeting_type' => 'email_greeting',
1093 );
1094 $emailGreetings = CRM_Core_PseudoConstant::greeting($emailGreetingFilter);
1095 $params['email_greeting'] = $emailGreetings[$params['email_greeting_id']];
1096 }
1097 else {
1098 $params['email_greeting'] = $values['email_greeting'];
1099 }
1100
1101 return TRUE;
1102 }
1103
1104 if (isset($values['postal_greeting'])) {
1105 if (!empty($params['postal_greeting_id'])) {
1106 $postalGreetingFilter = array(
1107 'contact_type' => CRM_Utils_Array::value('contact_type', $params),
1108 'greeting_type' => 'postal_greeting',
1109 );
1110 $postalGreetings = CRM_Core_PseudoConstant::greeting($postalGreetingFilter);
1111 $params['postal_greeting'] = $postalGreetings[$params['postal_greeting_id']];
1112 }
1113 else {
1114 $params['postal_greeting'] = $values['postal_greeting'];
1115 }
1116 return TRUE;
1117 }
1118
1119 if (isset($values['addressee'])) {
1120 if (!empty($params['addressee_id'])) {
1121 $addresseeFilter = array(
1122 'contact_type' => CRM_Utils_Array::value('contact_type', $params),
1123 'greeting_type' => 'addressee',
1124 );
1125 $addressee = CRM_Core_PseudoConstant::addressee($addresseeFilter);
1126 $params['addressee'] = $addressee[$params['addressee_id']];
1127 }
1128 else {
1129 $params['addressee'] = $values['addressee'];
1130 }
1131 return TRUE;
1132 }
1133
1134 if (isset($values['gender'])) {
1135 if (!empty($params['gender_id'])) {
1136 $genders = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id');
1137 $params['gender'] = $genders[$params['gender_id']];
1138 }
1139 else {
1140 $params['gender'] = $values['gender'];
1141 }
1142 return TRUE;
1143 }
1144
1145 if (!empty($values['preferred_communication_method'])) {
1146 $comm = array();
1147 $pcm = array_change_key_case(array_flip(CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method')), CASE_LOWER);
1148
1149 $preffComm = explode(',', $values['preferred_communication_method']);
1150 foreach ($preffComm as $v) {
1151 $v = strtolower(trim($v));
1152 if (array_key_exists($v, $pcm)) {
1153 $comm[$pcm[$v]] = 1;
1154 }
1155 }
1156
1157 $params['preferred_communication_method'] = $comm;
1158 return TRUE;
1159 }
1160
1161 // format the website params.
1162 if (!empty($values['url'])) {
1163 static $websiteFields;
1164 if (!is_array($websiteFields)) {
1165 $websiteFields = CRM_Core_DAO_Website::fields();
1166 }
1167 if (!array_key_exists('website', $params) ||
1168 !is_array($params['website'])
1169 ) {
1170 $params['website'] = array();
1171 }
1172
1173 $websiteCount = count($params['website']);
1174 _civicrm_api3_store_values($websiteFields, $values,
1175 $params['website'][++$websiteCount]
1176 );
1177
1178 return TRUE;
1179 }
1180
1181 // get the formatted location blocks into params - w/ 3.0 format, CRM-4605
1182 if (!empty($values['location_type_id'])) {
1183 $blockTypes = array(
1184 'phone' => 'Phone',
1185 'email' => 'Email',
1186 'im' => 'IM',
1187 'openid' => 'OpenID',
1188 'phone_ext' => 'Phone',
1189 );
1190 foreach ($blockTypes as $blockFieldName => $block) {
1191 if (!array_key_exists($blockFieldName, $values)) {
1192 continue;
1193 }
1194
1195 // block present in value array.
1196 if (!array_key_exists($blockFieldName, $params) || !is_array($params[$blockFieldName])) {
1197 $params[$blockFieldName] = array();
1198 }
1199
1200 if (!array_key_exists($block, $fields)) {
1201 $className = "CRM_Core_DAO_$block";
1202 $fields[$block] = $className::fields();
1203 }
1204
1205 $blockCnt = count($params[$blockFieldName]);
1206
1207 // copy value to dao field name.
1208 if ($blockFieldName == 'im') {
1209 $values['name'] = $values[$blockFieldName];
1210 }
1211
1212 _civicrm_api3_store_values($fields[$block], $values,
1213 $params[$blockFieldName][++$blockCnt]
1214 );
1215
1216 if ($values['location_type_id'] === 'Primary') {
1217 if (!empty($params['id'])) {
1218 $primary = civicrm_api3($block, 'get', array('return' => 'location_type_id', 'contact_id' => $params['id'], 'is_primary' => 1, 'sequential' => 1));
1219 }
1220 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
1221 $values['location_type_id'] = (isset($primary) && $primary['count']) ? $primary['values'][0]['location_type_id'] : $defaultLocationType->id;
1222 $values['is_primary'] = 1;
1223 }
1224
1225 if (empty($params['id']) && ($blockCnt == 1)) {
1226 $params[$blockFieldName][$blockCnt]['is_primary'] = TRUE;
1227 }
1228
1229 // we only process single block at a time.
1230 return TRUE;
1231 }
1232
1233 // handle address fields.
1234 if (!array_key_exists('address', $params) || !is_array($params['address'])) {
1235 $params['address'] = array();
1236 }
1237
1238 if (!array_key_exists('Address', $fields)) {
1239 $fields['Address'] = CRM_Core_DAO_Address::fields();
1240 }
1241
1242 // Note: we doing multiple value formatting here for address custom fields, plus putting into right format.
1243 // The actual formatting (like date, country ..etc) for address custom fields is taken care of while saving
1244 // the address in CRM_Core_BAO_Address::create method
1245 if (!empty($values['location_type_id'])) {
1246 static $customFields = array();
1247 if (empty($customFields)) {
1248 $customFields = CRM_Core_BAO_CustomField::getFields('Address');
1249 }
1250 // make a copy of values, as we going to make changes
1251 $newValues = $values;
1252 foreach ($values as $key => $val) {
1253 $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key);
1254 if ($customFieldID && array_key_exists($customFieldID, $customFields)) {
1255 // mark an entry in fields array since we want the value of custom field to be copied
1256 $fields['Address'][$key] = NULL;
1257
1258 $htmlType = CRM_Utils_Array::value('html_type', $customFields[$customFieldID]);
1259 switch ($htmlType) {
1260 case 'CheckBox':
1261 case 'AdvMulti-Select':
1262 case 'Multi-Select':
1263 if ($val) {
1264 $mulValues = explode(',', $val);
1265 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
1266 $newValues[$key] = array();
1267 foreach ($mulValues as $v1) {
1268 foreach ($customOption as $v2) {
1269 if ((strtolower($v2['label']) == strtolower(trim($v1))) ||
1270 (strtolower($v2['value']) == strtolower(trim($v1)))
1271 ) {
1272 if ($htmlType == 'CheckBox') {
1273 $newValues[$key][$v2['value']] = 1;
1274 }
1275 else {
1276 $newValues[$key][] = $v2['value'];
1277 }
1278 }
1279 }
1280 }
1281 }
1282 break;
1283 }
1284 }
1285 }
1286 // consider new values
1287 $values = $newValues;
1288 }
1289
1290 _civicrm_api3_store_values($fields['Address'], $values, $params['address'][$values['location_type_id']]);
1291
1292 $addressFields = array(
1293 'county',
1294 'country',
1295 'state_province',
1296 'supplemental_address_1',
1297 'supplemental_address_2',
1298 'supplemental_address_3',
1299 'StateProvince.name',
1300 );
1301
1302 foreach ($addressFields as $field) {
1303 if (array_key_exists($field, $values)) {
1304 if (!array_key_exists('address', $params)) {
1305 $params['address'] = array();
1306 }
1307 $params['address'][$values['location_type_id']][$field] = $values[$field];
1308 }
1309 }
1310
1311 if ($values['location_type_id'] === 'Primary') {
1312 if (!empty($params['id'])) {
1313 $primary = civicrm_api3('Address', 'get', array('return' => 'location_type_id', 'contact_id' => $params['id'], 'is_primary' => 1, 'sequential' => 1));
1314 }
1315 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
1316 $params['address'][$values['location_type_id']]['location_type_id'] = (isset($primary) && $primary['count']) ? $primary['values'][0]['location_type_id'] : $defaultLocationType->id;
1317 $params['address'][$values['location_type_id']]['is_primary'] = 1;
1318
1319 }
1320 return TRUE;
1321 }
1322
1323 if (isset($values['note'])) {
1324 // add a note field
1325 if (!isset($params['note'])) {
1326 $params['note'] = array();
1327 }
1328 $noteBlock = count($params['note']) + 1;
1329
1330 $params['note'][$noteBlock] = array();
1331 if (!isset($fields['Note'])) {
1332 $fields['Note'] = CRM_Core_DAO_Note::fields();
1333 }
1334
1335 // get the current logged in civicrm user
1336 $session = CRM_Core_Session::singleton();
1337 $userID = $session->get('userID');
1338
1339 if ($userID) {
1340 $values['contact_id'] = $userID;
1341 }
1342
1343 _civicrm_api3_store_values($fields['Note'], $values, $params['note'][$noteBlock]);
1344
1345 return TRUE;
1346 }
1347
1348 // Check for custom field values
1349
1350 if (empty($fields['custom'])) {
1351 $fields['custom'] = &CRM_Core_BAO_CustomField::getFields(CRM_Utils_Array::value('contact_type', $values),
1352 FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE
1353 );
1354 }
1355
1356 foreach ($values as $key => $value) {
1357 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
1358 // check if it's a valid custom field id
1359
1360 if (!array_key_exists($customFieldID, $fields['custom'])) {
1361 return civicrm_api3_create_error('Invalid custom field ID');
1362 }
1363 else {
1364 $params[$key] = $value;
1365 }
1366 }
1367 }
1368 return TRUE;
1369 }
1370
1371 }