CRM-12457
[civicrm-core.git] / CRM / Member / Import / Parser / Membership.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 require_once 'api/api.php';
38
39 /**
40 * class to parse membership csv files
41 */
42 class CRM_Member_Import_Parser_Membership extends CRM_Member_Import_Parser {
43
44 protected $_mapperKeys;
45
46 private $_contactIdIndex;
47 private $_totalAmountIndex;
48 private $_membershipTypeIndex;
49 private $_membershipStatusIndex;
50
51 /**
52 * Array of successfully imported membership id's
53 *
54 * @array
55 */
56 protected $_newMemberships;
57
58 /**
59 * class constructor
60 */
61 function __construct(&$mapperKeys, $mapperLocType = NULL, $mapperPhoneType = NULL) {
62 parent::__construct();
63 $this->_mapperKeys = &$mapperKeys;
64 }
65
66 /**
67 * the initializer code, called before the processing
68 *
69 * @return void
70 * @access public
71 */
72 function init() {
73 $fields = CRM_Member_BAO_Membership::importableFields($this->_contactType, FALSE);
74
75 foreach ($fields as $name => $field) {
76 $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT);
77 $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//');
78 $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//');
79 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
80 }
81
82 $this->_newMemberships = array();
83
84 $this->setActiveFields($this->_mapperKeys);
85
86 // FIXME: we should do this in one place together with Form/MapField.php
87 $this->_contactIdIndex = -1;
88 $this->_membershipTypeIndex = -1;
89 $this->_membershipStatusIndex = -1;
90
91 $index = 0;
92 foreach ($this->_mapperKeys as $key) {
93 switch ($key) {
94 case 'membership_contact_id':
95 $this->_contactIdIndex = $index;
96 break;
97
98 case 'membership_type_id':
99 $this->_membershipTypeIndex = $index;
100 break;
101
102 case 'status_id':
103 $this->_membershipStatusIndex = $index;
104 break;
105 }
106 $index++;
107 }
108 }
109
110 /**
111 * handle the values in mapField mode
112 *
113 * @param array $values the array of values belonging to this line
114 *
115 * @return boolean
116 * @access public
117 */
118 function mapField(&$values) {
119 return CRM_Member_Import_Parser::VALID;
120 }
121
122 /**
123 * handle the values in preview mode
124 *
125 * @param array $values the array of values belonging to this line
126 *
127 * @return boolean the result of this processing
128 * @access public
129 */
130 function preview(&$values) {
131 return $this->summary($values);
132 }
133
134 /**
135 * handle the values in summary mode
136 *
137 * @param array $values the array of values belonging to this line
138 *
139 * @return boolean the result of this processing
140 * @access public
141 */
142 function summary(&$values) {
143 $erroneousField = NULL;
144 $response = $this->setActiveFieldValues($values, $erroneousField);
145
146 $errorRequired = FALSE;
147
148 if ($this->_membershipTypeIndex < 0) {
149 $errorRequired = TRUE;
150 }
151 else {
152 $errorRequired = !CRM_Utils_Array::value($this->_membershipTypeIndex, $values);
153 }
154
155 if ($errorRequired) {
156 array_unshift($values, ts('Missing required fields'));
157 return CRM_Member_Import_Parser::ERROR;
158 }
159
160 $params = &$this->getActiveFieldParams();
161 $errorMessage = NULL;
162
163 //To check whether start date or join date is provided
164 if (!CRM_Utils_Array::value('membership_start_date', $params) && !CRM_Utils_Array::value('join_date', $params)) {
165 $errorMessage = 'Membership Start Date is required to create a memberships.';
166 CRM_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
167 }
168 //end
169
170 //for date-Formats
171 $session = CRM_Core_Session::singleton();
172 $dateType = $session->get('dateTypes');
173 foreach ($params as $key => $val) {
174
175 if ($val) {
176 switch ($key) {
177 case 'join_date':
178 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
179 if (!CRM_Utils_Rule::date($params[$key])) {
180 CRM_Import_Parser_Contact::addToErrorMsg('Member Since', $errorMessage);
181 }
182 }
183 else {
184 CRM_Import_Parser_Contact::addToErrorMsg('Member Since', $errorMessage);
185 }
186 break;
187
188 case 'membership_start_date':
189 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
190 if (!CRM_Utils_Rule::date($params[$key])) {
191 CRM_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
192 }
193 }
194 else {
195 CRM_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
196 }
197 break;
198
199 case 'membership_end_date':
200 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
201 if (!CRM_Utils_Rule::date($params[$key])) {
202 CRM_Import_Parser_Contact::addToErrorMsg('End date', $errorMessage);
203 }
204 }
205 else {
206 CRM_Import_Parser_Contact::addToErrorMsg('End date', $errorMessage);
207 }
208 break;
209
210 case 'membership_type_id':
211 $membershipTypes = CRM_Member_PseudoConstant::membershipType();
212 if (!CRM_Utils_Array::crmInArray($val, $membershipTypes) &&
213 !array_key_exists($val, $membershipTypes)
214 ) {
215 CRM_Import_Parser_Contact::addToErrorMsg('Membership Type', $errorMessage);
216 }
217 break;
218
219 case 'status_id':
220 if (!CRM_Utils_Array::crmInArray($val, CRM_Member_PseudoConstant::membershipStatus())) {
221 CRM_Import_Parser_Contact::addToErrorMsg('Membership Status', $errorMessage);
222 }
223 break;
224
225 case 'email':
226 if (!CRM_Utils_Rule::email($val)) {
227 CRM_Import_Parser_Contact::addToErrorMsg('Email Address', $errorMessage);
228 }
229 }
230 }
231 }
232 //date-Format part ends
233
234 $params['contact_type'] = 'Membership';
235
236 //checking error in custom data
237 CRM_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
238
239 if ($errorMessage) {
240 $tempMsg = "Invalid value for field(s) : $errorMessage";
241 array_unshift($values, $tempMsg);
242 $errorMessage = NULL;
243 return CRM_Import_Parser::ERROR;
244 }
245
246 return CRM_Member_Import_Parser::VALID;
247 }
248
249 /**
250 * handle the values in import mode
251 *
252 * @param int $onDuplicate the code for what action to take on duplicates
253 * @param array $values the array of values belonging to this line
254 *
255 * @return boolean the result of this processing
256 * @access public
257 */
258 function import($onDuplicate, &$values) {
259
260 // first make sure this is a valid line
261 $response = $this->summary($values);
262 if ($response != CRM_Member_Import_Parser::VALID) {
263 return $response;
264 }
265
266 $params = &$this->getActiveFieldParams();
267
268 //assign join date equal to start date if join date is not provided
269 if (!CRM_Utils_Array::value('join_date', $params) &&
270 CRM_Utils_Array::value('membership_start_date', $params)
271 ) {
272 $params['join_date'] = $params['membership_start_date'];
273 }
274
275 $session = CRM_Core_Session::singleton();
276 $dateType = $session->get('dateTypes');
277 $formatted = array();
278 $customFields = CRM_Core_BAO_CustomField::getFields(CRM_Utils_Array::value('contact_type', $params));
279
280 // don't add to recent items, CRM-4399
281 $formatted['skipRecentView'] = TRUE;
282
283 foreach ($params as $key => $val) {
284 if ($val) {
285 switch ($key) {
286 case 'join_date':
287 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
288 if (!CRM_Utils_Rule::date($params[$key])) {
289 CRM_Import_Parser_Contact::addToErrorMsg('Member Since', $errorMessage);
290 }
291 }
292 else {
293 CRM_Import_Parser_Contact::addToErrorMsg('Member Since', $errorMessage);
294 }
295 break;
296
297 case 'membership_start_date':
298 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
299 if (!CRM_Utils_Rule::date($params[$key])) {
300 CRM_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
301 }
302 }
303 else {
304 CRM_Import_Parser_Contact::addToErrorMsg('Start Date', $errorMessage);
305 }
306 break;
307
308 case 'membership_end_date':
309 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
310 if (!CRM_Utils_Rule::date($params[$key])) {
311 CRM_Import_Parser_Contact::addToErrorMsg('End Date', $errorMessage);
312 }
313 }
314 else {
315 CRM_Import_Parser_Contact::addToErrorMsg('End Date', $errorMessage);
316 }
317 break;
318
319 case 'membership_type_id':
320 if (!is_numeric($val)) {
321 unset($params['membership_type_id']);
322 $params['membership_type'] = $val;
323 }
324 break;
325
326 case 'status_id':
327 if (!is_numeric($val)) {
328 unset($params['status_id']);
329 $params['membership_status'] = $val;
330 }
331 break;
332
333 case 'is_override':
334 $params[$key] = CRM_Utils_String::strtobool($val);
335 break;
336 }
337 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
338 if ( $customFields[$customFieldID]['data_type'] == 'Date' ) {
339 CRM_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key);
340 unset($params[$key]);
341 } else if ( $customFields[$customFieldID]['data_type'] == 'Boolean' ) {
342 $params[$key] = CRM_Utils_String::strtoboolstr($val);
343 }
344 }
345 }
346 }
347 //date-Format part ends
348
349 static $indieFields = NULL;
350 if ($indieFields == NULL) {
351 $tempIndieFields = CRM_Member_DAO_Membership::import();
352 $indieFields = $tempIndieFields;
353 }
354
355 $formatValues = array();
356 foreach ($params as $key => $field) {
357 if ($field == NULL || $field === '') {
358 continue;
359 }
360
361 $formatValues[$key] = $field;
362 }
363 require_once 'CRM/Utils/DeprecatedUtils.php';
364 //TODO calling API function directly is unsupported.
365 $formatError = _civicrm_api3_deprecated_membership_format_params($formatValues, $formatted, TRUE);
366
367 if ($formatError) {
368 array_unshift($values, $formatError['error_message']);
369 return CRM_Member_Import_Parser::ERROR;
370 }
371
372 if ($onDuplicate != CRM_Member_Import_Parser::DUPLICATE_UPDATE) {
373 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
374 CRM_Core_DAO::$_nullObject,
375 NULL,
376 'Membership'
377 );
378 }
379 else {
380 //fix for CRM-2219 Update Membership
381 // onDuplicate == CRM_Member_Import_Parser::DUPLICATE_UPDATE
382 if (CRM_Utils_Array::value('is_override', $formatted) &&
383 !CRM_Utils_Array::value('status_id', $formatted)
384 ) {
385 array_unshift($values, 'Required parameter missing: Status');
386 return CRM_Member_Import_Parser::ERROR;
387 }
388
389 if ($formatValues['membership_id']) {
390 $dao = new CRM_Member_BAO_Membership();
391 $dao->id = $formatValues['membership_id'];
392 $dates = array('join_date', 'start_date', 'end_date');
393 foreach ($dates as $v) {
394 if (!CRM_Utils_Array::value( $v, $formatted )) {
395 $formatted[$v] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $formatValues['membership_id'], $v);
396 }
397 }
398
399 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
400 CRM_Core_DAO::$_nullObject,
401 $formatValues['membership_id'],
402 'Membership'
403 );
404 if ($dao->find(TRUE)) {
405 $ids = array(
406 'membership' => $formatValues['membership_id'],
407 'userId' => $session->get('userID'),
408 );
409
410 $newMembership = CRM_Member_BAO_Membership::create($formatted, $ids, TRUE);
411 if (civicrm_error($newMembership)) {
412 array_unshift($values, $newMembership['is_error'] . ' for Membership ID ' . $formatValues['membership_id'] . '. Row was skipped.');
413 return CRM_Member_Import_Parser::ERROR;
414 }
415 else {
416 $this->_newMemberships[] = $newMembership->id;
417 return CRM_Member_Import_Parser::VALID;
418 }
419 }
420 else {
421 array_unshift($values, 'Matching Membership record not found for Membership ID ' . $formatValues['membership_id'] . '. Row was skipped.');
422 return CRM_Member_Import_Parser::ERROR;
423 }
424 }
425 }
426
427 //Format dates
428 $startDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('start_date', $formatted), '%Y-%m-%d');
429 $endDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('end_date', $formatted), '%Y-%m-%d');
430 $joinDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('join_date', $formatted), '%Y-%m-%d');
431
432 if ($this->_contactIdIndex < 0) {
433
434 //retrieve contact id using contact dedupe rule
435 $formatValues['contact_type'] = $this->_contactType;
436 $formatValues['version'] = 3;
437 require_once 'CRM/Utils/DeprecatedUtils.php';
438 $error = _civicrm_api3_deprecated_check_contact_dedupe($formatValues);
439
440 if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
441 $matchedIDs = explode(',', $error['error_message']['params'][0]);
442 if (count($matchedIDs) > 1) {
443 array_unshift($values, 'Multiple matching contact records detected for this row. The membership was not imported');
444 return CRM_Member_Import_Parser::ERROR;
445 }
446 else {
447 $cid = $matchedIDs[0];
448 $formatted['contact_id'] = $cid;
449
450 //fix for CRM-1924
451 $calcDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($formatted['membership_type_id'],
452 $joinDate,
453 $startDate,
454 $endDate
455 );
456 self::formattedDates($calcDates, $formatted);
457
458 //fix for CRM-3570, exclude the statuses those having is_admin = 1
459 //now user can import is_admin if is override is true.
460 $excludeIsAdmin = FALSE;
461 if (!CRM_Utils_Array::value('is_override', $formatted)) {
462 $formatted['exclude_is_admin'] = $excludeIsAdmin = TRUE;
463 }
464 $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($startDate,
465 $endDate,
466 $joinDate,
467 'today',
468 $excludeIsAdmin
469 );
470
471 if (!CRM_Utils_Array::value('status_id', $formatted)) {
472 $formatted['status_id'] = $calcStatus['id'];
473 }
474 elseif (!CRM_Utils_Array::value('is_override', $formatted)) {
475 if (empty($calcStatus)) {
476 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.');
477 return CRM_Member_Import_Parser::ERROR;
478 }
479 elseif ($formatted['status_id'] != $calcStatus['id']) {
480 //Status Hold" is either NOT mapped or is FALSE
481 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.');
482 return CRM_Member_Import_Parser::ERROR;
483 }
484 }
485
486 $formatted['version'] = 3;
487 $newMembership = civicrm_api('membership', 'create', $formatted);
488 if (civicrm_error($newMembership)) {
489 array_unshift($values, $newMembership['error_message']);
490 return CRM_Member_Import_Parser::ERROR;
491 }
492
493 $this->_newMemberships[] = $newMembership['id'];
494 return CRM_Member_Import_Parser::VALID;
495 }
496 }
497 else {
498 // Using new Dedupe rule.
499 $ruleParams = array(
500 'contact_type' => $this->_contactType,
501 'used' => 'Unsupervised',
502 );
503 $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
504 $disp = '';
505
506 foreach ($fieldsArray as $value) {
507 if (array_key_exists(trim($value), $params)) {
508 $paramValue = $params[trim($value)];
509 if (is_array($paramValue)) {
510 $disp .= $params[trim($value)][0][trim($value)] . " ";
511 }
512 else {
513 $disp .= $params[trim($value)] . " ";
514 }
515 }
516 }
517
518 if (CRM_Utils_Array::value('external_identifier', $params)) {
519 if ($disp) {
520 $disp .= "AND {$params['external_identifier']}";
521 }
522 else {
523 $disp = $params['external_identifier'];
524 }
525 }
526
527 array_unshift($values, 'No matching Contact found for (' . $disp . ')');
528 return CRM_Member_Import_Parser::ERROR;
529 }
530 }
531 else {
532 if (CRM_Utils_Array::value('external_identifier', $formatValues)) {
533 $checkCid = new CRM_Contact_DAO_Contact();
534 $checkCid->external_identifier = $formatValues['external_identifier'];
535 $checkCid->find(TRUE);
536 if ($checkCid->id != $formatted['contact_id']) {
537 array_unshift($values, 'Mismatch of External identifier :' . $formatValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']);
538 return CRM_Member_Import_Parser::ERROR;
539 }
540 }
541
542 //to calculate dates
543 $calcDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($formatted['membership_type_id'],
544 $joinDate,
545 $startDate,
546 $endDate
547 );
548 self::formattedDates($calcDates, $formatted);
549 //end of date calculation part
550
551 //fix for CRM-3570, exclude the statuses those having is_admin = 1
552 //now user can import is_admin if is override is true.
553 $excludeIsAdmin = FALSE;
554 if (!CRM_Utils_Array::value('is_override', $formatted)) {
555 $formatted['exclude_is_admin'] = $excludeIsAdmin = TRUE;
556 }
557 $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($startDate,
558 $endDate,
559 $joinDate,
560 'today',
561 $excludeIsAdmin
562 );
563 if (!CRM_Utils_Array::value('status_id', $formatted)) {
564 $formatted['status_id'] = CRM_Utils_Array::value('id', $calcStatus);
565 }
566 elseif (!CRM_Utils_Array::value('is_override', $formatted)) {
567 if (empty($calcStatus)) {
568 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.');
569 return CRM_Member_Import_Parser::ERROR;
570 }
571 elseif ($formatted['status_id'] != $calcStatus['id']) {
572 //Status Hold" is either NOT mapped or is FALSE
573 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.');
574 return CRM_Member_Import_Parser::ERROR;
575 }
576 }
577
578 $formatted['version'] = 3;
579 $newMembership = civicrm_api('membership', 'create', $formatted);
580 if (civicrm_error($newMembership)) {
581 array_unshift($values, $newMembership['error_message']);
582 return CRM_Member_Import_Parser::ERROR;
583 }
584
585 $this->_newMemberships[] = $newMembership['id'];
586 return CRM_Member_Import_Parser::VALID;
587 }
588 }
589
590 /**
591 * Get the array of successfully imported membership id's
592 *
593 * @return array
594 * @access public
595 */
596 function &getImportedMemberships() {
597 return $this->_newMemberships;
598 }
599
600 /**
601 * the initializer code, called before the processing
602 *
603 * @return void
604 * @access public
605 */
606 function fini() {}
607
608 /**
609 * to calculate join, start and end dates
610 *
611 * @param Array $calcDates array of dates returned by getDatesForMembershipType()
612 *
613 * @return Array formatted containing date values
614 *
615 * @access public
616 */
617 function formattedDates($calcDates, &$formatted) {
618 $dates = array(
619 'join_date',
620 'start_date',
621 'end_date',
622 );
623
624 foreach ($dates as $d) {
625 if (isset($formatted[$d]) &&
626 !CRM_Utils_System::isNull($formatted[$d])
627 ) {
628 $formatted[$d] = CRM_Utils_Date::isoToMysql($formatted[$d]);
629 }
630 elseif (isset($calcDates[$d])) {
631 $formatted[$d] = CRM_Utils_Date::isoToMysql($calcDates[$d]);
632 }
633 }
634 }
635 }
636