[REF] [Import] Remove another good intention - conflict
[civicrm-core.git] / CRM / Member / Import / Parser / Membership.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
6a488035
TO
18/**
19 * class to parse membership csv files
20 */
ca4caf13 21class CRM_Member_Import_Parser_Membership extends CRM_Import_Parser {
6a488035
TO
22
23 protected $_mapperKeys;
24
6a488035
TO
25 private $_membershipTypeIndex;
26 private $_membershipStatusIndex;
27
14b9e069 28 /**
29 * Array of metadata for all available fields.
30 *
31 * @var array
32 */
33 protected $fieldMetadata = [];
34
6a488035 35 /**
ceb10dc7 36 * Array of successfully imported membership id's
6a488035 37 *
971e129b 38 * @var array
6a488035
TO
39 */
40 protected $_newMemberships;
41
ca4caf13
EM
42
43 protected $_fileName;
44
45 /**
46 * Imported file size
47 * @var int
48 */
49 protected $_fileSize;
50
51 /**
52 * Separator being used
53 * @var string
54 */
55 protected $_separator;
56
57 /**
58 * Total number of lines in file
59 * @var int
60 */
61 protected $_lineCount;
62
63 /**
64 * Whether the file has a column header or not
65 *
66 * @var bool
67 */
68 protected $_haveColumnHeader;
69
6a488035 70 /**
fe482240 71 * Class constructor.
c2b5a0af
EM
72 *
73 * @param $mapperKeys
6a488035 74 */
c2d1c1c2 75 public function __construct($mapperKeys = []) {
6a488035 76 parent::__construct();
14b9e069 77 $this->_mapperKeys = $mapperKeys;
6a488035
TO
78 }
79
ca4caf13
EM
80 /**
81 * @param string $fileName
82 * @param string $separator
83 * @param $mapper
84 * @param bool $skipColumnHeader
85 * @param int $mode
86 * @param int $contactType
87 * @param int $onDuplicate
88 * @param int $statusID
89 * @param int $totalRowCount
90 *
91 * @return mixed
92 * @throws Exception
93 */
94 public function run(
95 $fileName,
96 $separator,
97 $mapper,
98 $skipColumnHeader = FALSE,
99 $mode = self::MODE_PREVIEW,
100 $contactType = self::CONTACT_INDIVIDUAL,
101 $onDuplicate = self::DUPLICATE_SKIP,
102 $statusID = NULL,
103 $totalRowCount = NULL
104 ) {
105 if (!is_array($fileName)) {
106 throw new CRM_Core_Exception('Unable to determine import file');
107 }
108 $fileName = $fileName['name'];
109
110 switch ($contactType) {
111 case self::CONTACT_INDIVIDUAL:
112 $this->_contactType = 'Individual';
113 break;
114
115 case self::CONTACT_HOUSEHOLD:
116 $this->_contactType = 'Household';
117 break;
118
119 case self::CONTACT_ORGANIZATION:
120 $this->_contactType = 'Organization';
121 }
122
123 $this->init();
124
125 $this->_haveColumnHeader = $skipColumnHeader;
126
127 $this->_separator = $separator;
128
129 $fd = fopen($fileName, "r");
130 if (!$fd) {
131 return FALSE;
132 }
133
06ef1cdc 134 $this->_lineCount = 0;
ca4caf13 135 $this->_invalidRowCount = $this->_validCount = 0;
da8d3d49 136 $this->_totalCount = 0;
ca4caf13
EM
137
138 $this->_errors = [];
139 $this->_warnings = [];
ca4caf13
EM
140
141 $this->_fileSize = number_format(filesize($fileName) / 1024.0, 2);
142
143 if ($mode == self::MODE_MAPFIELD) {
144 $this->_rows = [];
145 }
146 else {
147 $this->_activeFieldCount = count($this->_activeFields);
148 }
149 if ($statusID) {
150 $this->progressImport($statusID);
151 $startTimestamp = $currTimestamp = $prevTimestamp = CRM_Utils_Time::time();
152 }
153
154 while (!feof($fd)) {
155 $this->_lineCount++;
156
157 $values = fgetcsv($fd, 8192, $separator);
158 if (!$values) {
159 continue;
160 }
161
162 self::encloseScrub($values);
163
164 // skip column header if we're not in mapfield mode
165 if ($mode != self::MODE_MAPFIELD && $skipColumnHeader) {
166 $skipColumnHeader = FALSE;
167 continue;
168 }
169
170 /* trim whitespace around the values */
171 $empty = TRUE;
172 foreach ($values as $k => $v) {
173 $values[$k] = trim($v, " \t\r\n");
174 }
175 if (CRM_Utils_System::isNull($values)) {
176 continue;
177 }
178
179 $this->_totalCount++;
180
181 if ($mode == self::MODE_MAPFIELD) {
4ad623fc 182 $returnCode = CRM_Import_Parser::VALID;
ca4caf13
EM
183 }
184 elseif ($mode == self::MODE_PREVIEW) {
185 $returnCode = $this->preview($values);
186 }
187 elseif ($mode == self::MODE_SUMMARY) {
188 $returnCode = $this->summary($values);
189 }
190 elseif ($mode == self::MODE_IMPORT) {
191 $returnCode = $this->import($onDuplicate, $values);
192 if ($statusID && (($this->_lineCount % 50) == 0)) {
193 $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount);
194 }
195 }
196 else {
197 $returnCode = self::ERROR;
198 }
199
200 // note that a line could be valid but still produce a warning
201 if ($returnCode & self::VALID) {
202 $this->_validCount++;
203 if ($mode == self::MODE_MAPFIELD) {
204 $this->_rows[] = $values;
205 $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
206 }
207 }
208
ca4caf13
EM
209 if ($returnCode & self::ERROR) {
210 $this->_invalidRowCount++;
211 $recordNumber = $this->_lineCount;
212 array_unshift($values, $recordNumber);
213 $this->_errors[] = $values;
214 }
215
ca4caf13
EM
216 if ($returnCode & self::DUPLICATE) {
217 $this->_duplicateCount++;
218 $recordNumber = $this->_lineCount;
219 array_unshift($values, $recordNumber);
220 $this->_duplicates[] = $values;
221 if ($onDuplicate != self::DUPLICATE_SKIP) {
222 $this->_validCount++;
223 }
224 }
225
ca4caf13
EM
226 // if we are done processing the maxNumber of lines, break
227 if ($this->_maxLinesToProcess > 0 && $this->_validCount >= $this->_maxLinesToProcess) {
228 break;
229 }
230 }
231
232 fclose($fd);
233
234 if ($mode == self::MODE_PREVIEW || $mode == self::MODE_IMPORT) {
235 $customHeaders = $mapper;
236
237 $customfields = CRM_Core_BAO_CustomField::getFields('Membership');
238 foreach ($customHeaders as $key => $value) {
239 if ($id = CRM_Core_BAO_CustomField::getKeyID($value)) {
240 $customHeaders[$key] = $customfields[$id][0];
241 }
242 }
243 if ($this->_invalidRowCount) {
244 // removed view url for invlaid contacts
245 $headers = array_merge([
246 ts('Line Number'),
247 ts('Reason'),
248 ], $customHeaders);
249 $this->_errorFileName = self::errorFileName(self::ERROR);
250
251 self::exportCSV($this->_errorFileName, $headers, $this->_errors);
252 }
ca4caf13
EM
253 if ($this->_duplicateCount) {
254 $headers = array_merge([
255 ts('Line Number'),
256 ts('View Membership URL'),
257 ], $customHeaders);
258
259 $this->_duplicateFileName = self::errorFileName(self::DUPLICATE);
260 self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates);
261 }
262 }
ca4caf13
EM
263 }
264
265 /**
266 * Given a list of the importable field keys that the user has selected
267 * set the active fields array to this list
268 *
269 * @param array $fieldKeys mapped array of values
270 *
271 * @return void
272 */
273 public function setActiveFields($fieldKeys) {
274 $this->_activeFieldCount = count($fieldKeys);
275 foreach ($fieldKeys as $key) {
276 if (empty($this->_fields[$key])) {
277 $this->_activeFields[] = new CRM_Member_Import_Field('', ts('- do not import -'));
278 }
279 else {
280 $this->_activeFields[] = clone($this->_fields[$key]);
281 }
282 }
283 }
284
285 /**
286 * Format the field values for input to the api.
287 *
288 * @return array
289 * (reference ) associative array of name/value pairs
290 */
291 public function &getActiveFieldParams() {
292 $params = [];
293 for ($i = 0; $i < $this->_activeFieldCount; $i++) {
294 if (isset($this->_activeFields[$i]->_value)
295 && !isset($params[$this->_activeFields[$i]->_name])
296 && !isset($this->_activeFields[$i]->_related)
297 ) {
298
299 $params[$this->_activeFields[$i]->_name] = $this->_activeFields[$i]->_value;
300 }
301 }
302 return $params;
303 }
304
305 /**
306 * @param string $name
307 * @param $title
308 * @param int $type
309 * @param string $headerPattern
310 * @param string $dataPattern
311 */
312 public function addField($name, $title, $type = CRM_Utils_Type::T_INT, $headerPattern = '//', $dataPattern = '//') {
313 if (empty($name)) {
314 $this->_fields['doNotImport'] = new CRM_Member_Import_Field($name, $title, $type, $headerPattern, $dataPattern);
315 }
316 else {
317
318 //$tempField = CRM_Contact_BAO_Contact::importableFields('Individual', null );
319 $tempField = CRM_Contact_BAO_Contact::importableFields('All', NULL);
320 if (!array_key_exists($name, $tempField)) {
321 $this->_fields[$name] = new CRM_Member_Import_Field($name, $title, $type, $headerPattern, $dataPattern);
322 }
323 else {
324 $this->_fields[$name] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern,
325 CRM_Utils_Array::value('hasLocationType', $tempField[$name])
326 );
327 }
328 }
329 }
330
331 /**
332 * Store parser values.
333 *
334 * @param CRM_Core_Session $store
335 *
336 * @param int $mode
337 *
338 * @return void
339 */
340 public function set($store, $mode = self::MODE_SUMMARY) {
341 $store->set('fileSize', $this->_fileSize);
342 $store->set('lineCount', $this->_lineCount);
343 $store->set('separator', $this->_separator);
344 $store->set('fields', $this->getSelectValues());
345 $store->set('fieldTypes', $this->getSelectTypes());
346
347 $store->set('headerPatterns', $this->getHeaderPatterns());
348 $store->set('dataPatterns', $this->getDataPatterns());
349 $store->set('columnCount', $this->_activeFieldCount);
350
351 $store->set('totalRowCount', $this->_totalCount);
352 $store->set('validRowCount', $this->_validCount);
353 $store->set('invalidRowCount', $this->_invalidRowCount);
ca4caf13
EM
354
355 switch ($this->_contactType) {
356 case 'Individual':
357 $store->set('contactType', CRM_Import_Parser::CONTACT_INDIVIDUAL);
358 break;
359
360 case 'Household':
361 $store->set('contactType', CRM_Import_Parser::CONTACT_HOUSEHOLD);
362 break;
363
364 case 'Organization':
365 $store->set('contactType', CRM_Import_Parser::CONTACT_ORGANIZATION);
366 }
367
368 if ($this->_invalidRowCount) {
369 $store->set('errorsFileName', $this->_errorFileName);
370 }
ca4caf13
EM
371 if (isset($this->_rows) && !empty($this->_rows)) {
372 $store->set('dataValues', $this->_rows);
373 }
374
375 if ($mode == self::MODE_IMPORT) {
376 $store->set('duplicateRowCount', $this->_duplicateCount);
377 if ($this->_duplicateCount) {
378 $store->set('duplicatesFileName', $this->_duplicateFileName);
379 }
380 }
381 }
382
383 /**
384 * Export data to a CSV file.
385 *
386 * @param string $fileName
387 * @param array $header
388 * @param array $data
389 *
390 * @return void
391 */
392 public static function exportCSV($fileName, $header, $data) {
393 $output = [];
394 $fd = fopen($fileName, 'w');
395
396 foreach ($header as $key => $value) {
397 $header[$key] = "\"$value\"";
398 }
399 $config = CRM_Core_Config::singleton();
400 $output[] = implode($config->fieldSeparator, $header);
401
402 foreach ($data as $datum) {
403 foreach ($datum as $key => $value) {
404 if (is_array($value)) {
405 foreach ($value[0] as $k1 => $v1) {
406 if ($k1 == 'location_type_id') {
407 continue;
408 }
409 $datum[$k1] = $v1;
410 }
411 }
412 else {
413 $datum[$key] = "\"$value\"";
414 }
415 }
416 $output[] = implode($config->fieldSeparator, $datum);
417 }
418 fwrite($fd, implode("\n", $output));
419 fclose($fd);
420 }
421
6a488035 422 /**
100fef9d 423 * The initializer code, called before the processing
6a488035
TO
424 *
425 * @return void
6a488035 426 */
00be9182 427 public function init() {
14b9e069 428 $this->fieldMetadata = CRM_Member_BAO_Membership::importableFields($this->_contactType, FALSE);
6a488035 429
14b9e069 430 foreach ($this->fieldMetadata as $name => $field) {
431 // @todo - we don't really need to do all this.... fieldMetadata is just fine to use as is.
6a488035
TO
432 $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT);
433 $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//');
434 $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//');
435 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
436 }
437
be2fb01f 438 $this->_newMemberships = [];
6a488035
TO
439
440 $this->setActiveFields($this->_mapperKeys);
441
442 // FIXME: we should do this in one place together with Form/MapField.php
6a488035
TO
443 $this->_membershipTypeIndex = -1;
444 $this->_membershipStatusIndex = -1;
445
446 $index = 0;
447 foreach ($this->_mapperKeys as $key) {
448 switch ($key) {
6a488035
TO
449
450 case 'membership_type_id':
451 $this->_membershipTypeIndex = $index;
452 break;
453
454 case 'status_id':
455 $this->_membershipStatusIndex = $index;
456 break;
457 }
458 $index++;
459 }
460 }
461
6a488035 462 /**
fe482240 463 * Handle the values in preview mode.
6a488035 464 *
b2363ea8
TO
465 * @param array $values
466 * The array of values belonging to this line.
6a488035 467 *
d5cc0fc2 468 * @return bool
a6c01b45 469 * the result of this processing
6a488035 470 */
00be9182 471 public function preview(&$values) {
6a488035
TO
472 return $this->summary($values);
473 }
474
475 /**
fe482240 476 * Handle the values in summary mode.
6a488035 477 *
b2363ea8
TO
478 * @param array $values
479 * The array of values belonging to this line.
6a488035 480 *
d5cc0fc2 481 * @return bool
a6c01b45 482 * the result of this processing
6a488035 483 */
00be9182 484 public function summary(&$values) {
1006edc9
EM
485
486 $this->setActiveFieldValues($values);
6a488035
TO
487
488 $errorRequired = FALSE;
489
490 if ($this->_membershipTypeIndex < 0) {
491 $errorRequired = TRUE;
492 }
493 else {
494 $errorRequired = !CRM_Utils_Array::value($this->_membershipTypeIndex, $values);
495 }
496
497 if ($errorRequired) {
498 array_unshift($values, ts('Missing required fields'));
a05662ef 499 return CRM_Import_Parser::ERROR;
6a488035
TO
500 }
501
04f72de8 502 $params = $this->getActiveFieldParams();
6a488035
TO
503 $errorMessage = NULL;
504
505 //To check whether start date or join date is provided
c8dcfe3a 506 if (empty($params['membership_start_date']) && empty($params['membership_join_date'])) {
6a488035 507 $errorMessage = 'Membership Start Date is required to create a memberships.';
719a6fec 508 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
6a488035 509 }
6a488035
TO
510
511 //for date-Formats
512 $session = CRM_Core_Session::singleton();
513 $dateType = $session->get('dateTypes');
514 foreach ($params as $key => $val) {
515
516 if ($val) {
517 switch ($key) {
c8dcfe3a 518 case 'membership_join_date':
6a488035
TO
519 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
520 if (!CRM_Utils_Rule::date($params[$key])) {
719a6fec 521 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Member Since', $errorMessage);
6a488035
TO
522 }
523 }
524 else {
719a6fec 525 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Member Since', $errorMessage);
6a488035
TO
526 }
527 break;
528
529 case 'membership_start_date':
530 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
531 if (!CRM_Utils_Rule::date($params[$key])) {
719a6fec 532 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
6a488035
TO
533 }
534 }
535 else {
719a6fec 536 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
6a488035
TO
537 }
538 break;
539
540 case 'membership_end_date':
541 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
542 if (!CRM_Utils_Rule::date($params[$key])) {
719a6fec 543 CRM_Contact_Import_Parser_Contact::addToErrorMsg('End date', $errorMessage);
6a488035
TO
544 }
545 }
546 else {
719a6fec 547 CRM_Contact_Import_Parser_Contact::addToErrorMsg('End date', $errorMessage);
6a488035
TO
548 }
549 break;
e136f704
O
550
551 case 'status_override_end_date':
552 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
553 if (!CRM_Utils_Rule::date($params[$key])) {
554 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Status Override End Date', $errorMessage);
555 }
556 }
557 else {
558 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Status Override End Date', $errorMessage);
559 }
560 break;
6a488035
TO
561
562 case 'membership_type_id':
14b9e069 563 // @todo - squish into membership status - can use same lines here too.
6a488035
TO
564 $membershipTypes = CRM_Member_PseudoConstant::membershipType();
565 if (!CRM_Utils_Array::crmInArray($val, $membershipTypes) &&
566 !array_key_exists($val, $membershipTypes)
567 ) {
719a6fec 568 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Membership Type', $errorMessage);
6a488035
TO
569 }
570 break;
571
572 case 'status_id':
14b9e069 573 if (!empty($val) && !$this->parsePseudoConstantField($val, $this->fieldMetadata[$key])) {
719a6fec 574 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Membership Status', $errorMessage);
6a488035
TO
575 }
576 break;
577
578 case 'email':
579 if (!CRM_Utils_Rule::email($val)) {
719a6fec 580 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Email Address', $errorMessage);
6a488035
TO
581 }
582 }
583 }
584 }
585 //date-Format part ends
586
587 $params['contact_type'] = 'Membership';
588
589 //checking error in custom data
719a6fec 590 CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
6a488035
TO
591
592 if ($errorMessage) {
593 $tempMsg = "Invalid value for field(s) : $errorMessage";
594 array_unshift($values, $tempMsg);
595 $errorMessage = NULL;
a05662ef 596 return CRM_Import_Parser::ERROR;
6a488035
TO
597 }
598
a05662ef 599 return CRM_Import_Parser::VALID;
6a488035
TO
600 }
601
602 /**
fe482240 603 * Handle the values in import mode.
6a488035 604 *
b2363ea8
TO
605 * @param int $onDuplicate
606 * The code for what action to take on duplicates.
607 * @param array $values
608 * The array of values belonging to this line.
6a488035 609 *
d5cc0fc2 610 * @return bool
a6c01b45 611 * the result of this processing
6a488035 612 */
00be9182 613 public function import($onDuplicate, &$values) {
92e4c2a5 614 try {
4f7b71ab 615 // first make sure this is a valid line
616 $response = $this->summary($values);
617 if ($response != CRM_Import_Parser::VALID) {
618 return $response;
619 }
6a488035 620
04f72de8 621 $params = $this->getActiveFieldParams();
6a488035 622
4f7b71ab 623 //assign join date equal to start date if join date is not provided
c8dcfe3a
SL
624 if (empty($params['membership_join_date']) && !empty($params['membership_start_date'])) {
625 $params['membership_join_date'] = $params['membership_start_date'];
4f7b71ab 626 }
6a488035 627
353ffa53 628 $session = CRM_Core_Session::singleton();
24a67831 629 $dateType = CRM_Core_Session::singleton()->get('dateTypes');
be2fb01f 630 $formatted = [];
5981d156
JP
631 $customDataType = !empty($params['contact_type']) ? $params['contact_type'] : 'Membership';
632 $customFields = CRM_Core_BAO_CustomField::getFields($customDataType);
4f7b71ab 633
634 // don't add to recent items, CRM-4399
635 $formatted['skipRecentView'] = TRUE;
be2fb01f 636 $dateLabels = [
c8dcfe3a 637 'membership_join_date' => ts('Member Since'),
4f7b71ab 638 'membership_start_date' => ts('Start Date'),
639 'membership_end_date' => ts('End Date'),
be2fb01f 640 ];
4f7b71ab 641 foreach ($params as $key => $val) {
642 if ($val) {
643 switch ($key) {
c8dcfe3a 644 case 'membership_join_date':
4f7b71ab 645 case 'membership_start_date':
646 case 'membership_end_date':
647 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
648 if (!CRM_Utils_Rule::date($params[$key])) {
649 CRM_Contact_Import_Parser_Contact::addToErrorMsg($dateLabels[$key], $errorMessage);
650 }
651 }
652 else {
87bbc876 653 CRM_Contact_Import_Parser_Contact::addToErrorMsg($dateLabels[$key], $errorMessage);
6a488035 654 }
4f7b71ab 655 break;
6a488035 656
4f7b71ab 657 case 'membership_type_id':
658 if (!is_numeric($val)) {
659 unset($params['membership_type_id']);
660 $params['membership_type'] = $val;
661 }
662 break;
6a488035 663
4f7b71ab 664 case 'status_id':
14b9e069 665 // @todo - we can do this based on the presence of 'pseudoconstant' in the metadata rather than field specific.
666 $params[$key] = $this->parsePseudoConstantField($val, $this->fieldMetadata[$key]);
4f7b71ab 667 break;
6a488035 668
4f7b71ab 669 }
670 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
481a74f4 671 if ($customFields[$customFieldID]['data_type'] == 'Date') {
4f7b71ab 672 CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key);
673 unset($params[$key]);
0db6c3e1 674 }
481a74f4 675 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
4f7b71ab 676 $params[$key] = CRM_Utils_String::strtoboolstr($val);
677 }
6a488035
TO
678 }
679 }
680 }
4f7b71ab 681 //date-Format part ends
6a488035 682
be2fb01f 683 $formatValues = [];
4f7b71ab 684 foreach ($params as $key => $field) {
19cea1b4 685 // ignore empty values or empty arrays etc
686 if (CRM_Utils_System::isNull($field)) {
4f7b71ab 687 continue;
688 }
6a488035 689
4f7b71ab 690 $formatValues[$key] = $field;
6a488035
TO
691 }
692
4f7b71ab 693 //format params to meet api v2 requirements.
694 //@todo find a way to test removing this formatting
695 $formatError = $this->membership_format_params($formatValues, $formatted, TRUE);
6a488035 696
4f7b71ab 697 if ($onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE) {
6a488035 698 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
4f7b71ab 699 NULL,
6a488035
TO
700 'Membership'
701 );
4f7b71ab 702 }
703 else {
704 //fix for CRM-2219 Update Membership
705 // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE
8f67d99a 706 if (!empty($formatted['member_is_override']) && empty($formatted['status_id'])) {
4f7b71ab 707 array_unshift($values, 'Required parameter missing: Status');
708 return CRM_Import_Parser::ERROR;
709 }
6a488035 710
1a9d4317 711 if (!empty($formatValues['membership_id'])) {
353ffa53 712 $dao = new CRM_Member_BAO_Membership();
4f7b71ab 713 $dao->id = $formatValues['membership_id'];
be2fb01f 714 $dates = ['join_date', 'start_date', 'end_date'];
4f7b71ab 715 foreach ($dates as $v) {
82cc6775 716 if (empty($formatted[$v])) {
4f7b71ab 717 $formatted[$v] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $formatValues['membership_id'], $v);
718 }
719 }
720
721 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
4f7b71ab 722 $formatValues['membership_id'],
723 'Membership'
724 );
725 if ($dao->find(TRUE)) {
82cc6775
PN
726 if (empty($params['line_item']) && !empty($formatted['membership_type_id'])) {
727 CRM_Price_BAO_LineItem::getLineItemArray($formatted, NULL, 'membership', $formatted['membership_type_id']);
728 }
d75f2f47 729
f3a5127d 730 $newMembership = civicrm_api3('Membership', 'create', $formatted);
731 $this->_newMemberships[] = $newMembership['id'];
732 return CRM_Import_Parser::VALID;
6a488035
TO
733 }
734 else {
4f7b71ab 735 array_unshift($values, 'Matching Membership record not found for Membership ID ' . $formatValues['membership_id'] . '. Row was skipped.');
736 return CRM_Import_Parser::ERROR;
6a488035
TO
737 }
738 }
6a488035 739 }
6a488035 740
4f7b71ab 741 //Format dates
742 $startDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('start_date', $formatted), '%Y-%m-%d');
353ffa53
TO
743 $endDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('end_date', $formatted), '%Y-%m-%d');
744 $joinDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('join_date', $formatted), '%Y-%m-%d');
6a488035 745
64679927 746 if (!$this->isContactIDColumnPresent()) {
56316747 747 $error = $this->checkContactDuplicate($formatValues);
6a488035 748
4f7b71ab 749 if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
750 $matchedIDs = explode(',', $error['error_message']['params'][0]);
751 if (count($matchedIDs) > 1) {
752 array_unshift($values, 'Multiple matching contact records detected for this row. The membership was not imported');
753 return CRM_Import_Parser::ERROR;
754 }
755 else {
756 $cid = $matchedIDs[0];
757 $formatted['contact_id'] = $cid;
758
759 //fix for CRM-1924
760 $calcDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($formatted['membership_type_id'],
761 $joinDate,
762 $startDate,
763 $endDate
764 );
765 self::formattedDates($calcDates, $formatted);
766
767 //fix for CRM-3570, exclude the statuses those having is_admin = 1
768 //now user can import is_admin if is override is true.
769 $excludeIsAdmin = FALSE;
8f67d99a 770 if (empty($formatted['member_is_override'])) {
4f7b71ab 771 $formatted['exclude_is_admin'] = $excludeIsAdmin = TRUE;
772 }
773 $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($startDate,
774 $endDate,
775 $joinDate,
2cb64970 776 'now',
5f11bbcc
EM
777 $excludeIsAdmin,
778 $formatted['membership_type_id'],
779 $formatted
4f7b71ab 780 );
781
a7488080 782 if (empty($formatted['status_id'])) {
4f7b71ab 783 $formatted['status_id'] = $calcStatus['id'];
784 }
8f67d99a 785 elseif (empty($formatted['member_is_override'])) {
4f7b71ab 786 if (empty($calcStatus)) {
787 array_unshift($values, 'Status in import row (' . $formatValues['status_id'] . ') does not match calculated status based on your configured Membership Status Rules. Record was not imported.');
788 return CRM_Import_Parser::ERROR;
789 }
790 elseif ($formatted['status_id'] != $calcStatus['id']) {
791 //Status Hold" is either NOT mapped or is FALSE
792 array_unshift($values, 'Status in import row (' . $formatValues['status_id'] . ') does not match calculated status based on your configured Membership Status Rules (' . $calcStatus['name'] . '). Record was not imported.');
793 return CRM_Import_Parser::ERROR;
794 }
795 }
796
797 $newMembership = civicrm_api3('membership', 'create', $formatted);
798
799 $this->_newMemberships[] = $newMembership['id'];
800 return CRM_Import_Parser::VALID;
801 }
6a488035
TO
802 }
803 else {
4f7b71ab 804 // Using new Dedupe rule.
be2fb01f 805 $ruleParams = [
4f7b71ab 806 'contact_type' => $this->_contactType,
353ffa53 807 'used' => 'Unsupervised',
be2fb01f 808 ];
61194d45 809 $fieldsArray = CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams);
4f7b71ab 810 $disp = '';
811
812 foreach ($fieldsArray as $value) {
813 if (array_key_exists(trim($value), $params)) {
814 $paramValue = $params[trim($value)];
815 if (is_array($paramValue)) {
816 $disp .= $params[trim($value)][0][trim($value)] . " ";
817 }
818 else {
819 $disp .= $params[trim($value)] . " ";
820 }
821 }
6a488035 822 }
6a488035 823
a7488080 824 if (!empty($params['external_identifier'])) {
4f7b71ab 825 if ($disp) {
826 $disp .= "AND {$params['external_identifier']}";
6a488035 827 }
4f7b71ab 828 else {
829 $disp = $params['external_identifier'];
6a488035
TO
830 }
831 }
832
4f7b71ab 833 array_unshift($values, 'No matching Contact found for (' . $disp . ')');
834 return CRM_Import_Parser::ERROR;
6a488035
TO
835 }
836 }
837 else {
a7488080 838 if (!empty($formatValues['external_identifier'])) {
4f7b71ab 839 $checkCid = new CRM_Contact_DAO_Contact();
840 $checkCid->external_identifier = $formatValues['external_identifier'];
841 $checkCid->find(TRUE);
842 if ($checkCid->id != $formatted['contact_id']) {
d79be26c 843 array_unshift($values, 'Mismatch of External ID:' . $formatValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']);
4f7b71ab 844 return CRM_Import_Parser::ERROR;
6a488035
TO
845 }
846 }
847
4f7b71ab 848 //to calculate dates
849 $calcDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($formatted['membership_type_id'],
850 $joinDate,
851 $startDate,
852 $endDate
853 );
854 self::formattedDates($calcDates, $formatted);
855 //end of date calculation part
856
857 //fix for CRM-3570, exclude the statuses those having is_admin = 1
858 //now user can import is_admin if is override is true.
859 $excludeIsAdmin = FALSE;
8f67d99a 860 if (empty($formatted['member_is_override'])) {
4f7b71ab 861 $formatted['exclude_is_admin'] = $excludeIsAdmin = TRUE;
862 }
863 $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($startDate,
864 $endDate,
865 $joinDate,
2cb64970 866 'now',
5f11bbcc
EM
867 $excludeIsAdmin,
868 $formatted['membership_type_id'],
869 $formatted
4f7b71ab 870 );
a7488080 871 if (empty($formatted['status_id'])) {
9c1bc317 872 $formatted['status_id'] = $calcStatus['id'] ?? NULL;
4f7b71ab 873 }
8f67d99a 874 elseif (empty($formatted['member_is_override'])) {
4f7b71ab 875 if (empty($calcStatus)) {
876 array_unshift($values, 'Status in import row (' . CRM_Utils_Array::value('status_id', $formatValues) . ') does not match calculated status based on your configured Membership Status Rules. Record was not imported.');
877 return CRM_Import_Parser::ERROR;
6a488035 878 }
4f7b71ab 879 elseif ($formatted['status_id'] != $calcStatus['id']) {
880 //Status Hold" is either NOT mapped or is FALSE
881 array_unshift($values, 'Status in import row (' . CRM_Utils_Array::value('status_id', $formatValues) . ') does not match calculated status based on your configured Membership Status Rules (' . $calcStatus['name'] . '). Record was not imported.');
882 return CRM_Import_Parser::ERROR;
6a488035
TO
883 }
884 }
885
4f7b71ab 886 $newMembership = civicrm_api3('membership', 'create', $formatted);
6a488035 887
4f7b71ab 888 $this->_newMemberships[] = $newMembership['id'];
889 return CRM_Import_Parser::VALID;
6a488035 890 }
f719e41c 891 }
892 catch (Exception $e) {
893 array_unshift($values, $e->getMessage());
894 return CRM_Import_Parser::ERROR;
895 }
6a488035
TO
896 }
897
898 /**
ceb10dc7 899 * Get the array of successfully imported membership id's
6a488035
TO
900 *
901 * @return array
6a488035 902 */
00be9182 903 public function &getImportedMemberships() {
6a488035
TO
904 return $this->_newMemberships;
905 }
906
6a488035
TO
907 /**
908 * to calculate join, start and end dates
909 *
b2363ea8
TO
910 * @param array $calcDates
911 * Array of dates returned by getDatesForMembershipType().
6a488035 912 *
2a6da8d7 913 * @param $formatted
6a488035 914 *
6a488035 915 */
00be9182 916 public function formattedDates($calcDates, &$formatted) {
be2fb01f 917 $dates = [
6a488035
TO
918 'join_date',
919 'start_date',
920 'end_date',
be2fb01f 921 ];
6a488035
TO
922
923 foreach ($dates as $d) {
924 if (isset($formatted[$d]) &&
925 !CRM_Utils_System::isNull($formatted[$d])
926 ) {
927 $formatted[$d] = CRM_Utils_Date::isoToMysql($formatted[$d]);
928 }
929 elseif (isset($calcDates[$d])) {
930 $formatted[$d] = CRM_Utils_Date::isoToMysql($calcDates[$d]);
931 }
932 }
933 }
77b97be7 934
3c15495c 935 /**
936 * @deprecated - this function formats params according to v2 standards but
937 * need to be sure about the impact of not calling it so retaining on the import class
938 * take the input parameter list as specified in the data model and
939 * convert it into the same format that we use in QF and BAO object
940 *
b2363ea8
TO
941 * @param array $params
942 * Associative array of property name/value.
3c15495c 943 * pairs to insert in new contact.
b2363ea8
TO
944 * @param array $values
945 * The reformatted properties that we can use internally.
3c15495c 946 *
77b97be7 947 * @param array|bool $create Is the formatted Values array going to
3c15495c 948 * be used for CRM_Member_BAO_Membership:create()
949 *
77b97be7 950 * @throws Exception
3c15495c 951 * @return array|error
3c15495c 952 */
00be9182 953 public function membership_format_params($params, &$values, $create = FALSE) {
3c15495c 954 require_once 'api/v3/utils.php';
955 $fields = CRM_Member_DAO_Membership::fields();
956 _civicrm_api3_store_values($fields, $params, $values);
957
481a74f4 958 $customFields = CRM_Core_BAO_CustomField::getFields('Membership');
3c15495c 959
960 foreach ($params as $key => $value) {
3c15495c 961
962 //Handling Custom Data
963 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
964 $values[$key] = $value;
965 $type = $customFields[$customFieldID]['html_type'];
726e45e7 966 if (CRM_Core_BAO_CustomField::isSerialized($customFields[$customFieldID])) {
be40742b 967 $values[$key] = self::unserializeCustomValue($customFieldID, $value, $type);
3c15495c 968 }
969 }
970
971 switch ($key) {
972 case 'membership_contact_id':
973 if (!CRM_Utils_Rule::integer($value)) {
f719e41c 974 throw new Exception("contact_id not valid: $value");
3c15495c 975 }
353ffa53 976 $dao = new CRM_Core_DAO();
be2fb01f 977 $qParams = [];
353ffa53 978 $svq = $dao->singleValueQuery("SELECT id FROM civicrm_contact WHERE id = $value",
3c15495c 979 $qParams
980 );
981 if (!$svq) {
f719e41c 982 throw new Exception("Invalid Contact ID: There is no contact record with contact_id = $value.");
3c15495c 983 }
984 $values['contact_id'] = $values['membership_contact_id'];
985 unset($values['membership_contact_id']);
986 break;
987
988 case 'membership_type_id':
f3acfdd9 989 if (!array_key_exists($value, CRM_Member_PseudoConstant::membershipType())) {
f719e41c 990 throw new Exception('Invalid Membership Type Id');
3c15495c 991 }
992 $values[$key] = $value;
993 break;
994
995 case 'membership_type':
996 $membershipTypeId = CRM_Utils_Array::key(ucfirst($value),
353ffa53 997 CRM_Member_PseudoConstant::membershipType()
3c15495c 998 );
999 if ($membershipTypeId) {
a7488080 1000 if (!empty($values['membership_type_id']) &&
3c15495c 1001 $membershipTypeId != $values['membership_type_id']
1002 ) {
f719e41c 1003 throw new Exception('Mismatched membership Type and Membership Type Id');
3c15495c 1004 }
1005 }
1006 else {
f719e41c 1007 throw new Exception('Invalid Membership Type');
3c15495c 1008 }
1009 $values['membership_type_id'] = $membershipTypeId;
1010 break;
1011
3c15495c 1012 default:
1013 break;
1014 }
1015 }
1016
3c15495c 1017 if ($create) {
c8dcfe3a 1018 // CRM_Member_BAO_Membership::create() handles membership_start_date, membership_join_date,
3c15495c 1019 // membership_end_date and membership_source. So, if $values contains
c8dcfe3a
SL
1020 // membership_start_date, membership_end_date, membership_join_date or membership_source,
1021 // convert it to start_date, end_date, join_date or source
be2fb01f 1022 $changes = [
c8dcfe3a 1023 'membership_join_date' => 'join_date',
3c15495c 1024 'membership_start_date' => 'start_date',
1025 'membership_end_date' => 'end_date',
1026 'membership_source' => 'source',
be2fb01f 1027 ];
3c15495c 1028
1029 foreach ($changes as $orgVal => $changeVal) {
1030 if (isset($values[$orgVal])) {
1031 $values[$changeVal] = $values[$orgVal];
1032 unset($values[$orgVal]);
1033 }
1034 }
1035 }
1036
1037 return NULL;
1038 }
96025800 1039
64679927 1040 /**
1041 * Is the contact ID mapped.
1042 *
1043 * @return bool
1044 */
1045 protected function isContactIDColumnPresent(): bool {
1046 return in_array('membership_contact_id', $this->_mapperKeys, TRUE);
1047 }
1048
6a488035 1049}