* @copyright CiviCRM LLC https://civicrm.org/licensing
*/
+use Civi\Api4\Contact;
+use Civi\Api4\Email;
+
/**
* Class to parse contribution csv files.
*/
protected $_mapperKeys;
- private $_contactIdIndex;
-
- protected $_mapperSoftCredit;
- //protected $_mapperPhoneType;
-
/**
* Array of successfully imported contribution id's
*
* Class constructor.
*
* @param $mapperKeys
- * @param array $mapperSoftCredit
- * @param null $mapperPhoneType
- * @param array $mapperSoftCreditType
*/
- public function __construct(&$mapperKeys = [], $mapperSoftCredit = [], $mapperPhoneType = NULL, $mapperSoftCreditType = []) {
+ public function __construct($mapperKeys = []) {
parent::__construct();
- $this->_mapperKeys = &$mapperKeys;
- $this->_mapperSoftCredit = &$mapperSoftCredit;
- $this->_mapperSoftCreditType = &$mapperSoftCreditType;
+ $this->_mapperKeys = $mapperKeys;
}
/**
*/
const SOFT_CREDIT = 512, SOFT_CREDIT_ERROR = 1024, PLEDGE_PAYMENT = 2048, PLEDGE_PAYMENT_ERROR = 4096;
- /**
- * @var string
- */
- protected $_fileName;
-
- /**
- * Imported file size
- * @var int
- */
- protected $_fileSize;
-
/**
* Separator being used
* @var string
protected $_softCreditErrorsFileName;
/**
- * Whether the file has a column header or not
+ * Get the field mappings for the import.
*
- * @var bool
- */
- protected $_haveColumnHeader;
-
- /**
- * @param string $fileName
- * @param string $separator
- * @param $mapper
- * @param bool $skipColumnHeader
- * @param int $mode
- * @param int $contactType
- * @param int $onDuplicate
- * @param int $statusID
- * @param int $totalRowCount
- *
- * @return mixed
- * @throws Exception
- */
- public function run(
- $fileName,
- $separator,
- $mapper,
- $skipColumnHeader = FALSE,
- $mode = self::MODE_PREVIEW,
- $contactType = self::CONTACT_INDIVIDUAL,
- $onDuplicate = self::DUPLICATE_SKIP,
- $statusID = NULL,
- $totalRowCount = NULL
- ) {
- if (!is_array($fileName)) {
- throw new CRM_Core_Exception('Unable to determine import file');
- }
- $fileName = $fileName['name'];
-
- switch ($contactType) {
- case self::CONTACT_INDIVIDUAL:
- $this->_contactType = 'Individual';
- break;
-
- case self::CONTACT_HOUSEHOLD:
- $this->_contactType = 'Household';
- break;
-
- case self::CONTACT_ORGANIZATION:
- $this->_contactType = 'Organization';
- }
-
- $this->init();
-
- $this->_haveColumnHeader = $skipColumnHeader;
-
- $this->_separator = $separator;
-
- $fd = fopen($fileName, "r");
- if (!$fd) {
- return FALSE;
- }
-
- $this->_lineCount = $this->_validSoftCreditRowCount = $this->_validPledgePaymentRowCount = 0;
- $this->_invalidRowCount = $this->_validCount = $this->_invalidSoftCreditRowCount = $this->_invalidPledgePaymentRowCount = 0;
- $this->_totalCount = 0;
-
- $this->_errors = [];
- $this->_warnings = [];
- $this->_pledgePaymentErrors = [];
- $this->_softCreditErrors = [];
- if ($statusID) {
- $this->progressImport($statusID);
- $startTimestamp = $currTimestamp = $prevTimestamp = time();
- }
-
- $this->_fileSize = number_format(filesize($fileName) / 1024.0, 2);
-
- if ($mode == self::MODE_MAPFIELD) {
- $this->_rows = [];
- }
- else {
- $this->_activeFieldCount = count($this->_activeFields);
- }
-
- while (!feof($fd)) {
- $this->_lineCount++;
-
- $values = fgetcsv($fd, 8192, $separator);
- if (!$values) {
- continue;
- }
-
- self::encloseScrub($values);
-
- // skip column header if we're not in mapfield mode
- if ($mode != self::MODE_MAPFIELD && $skipColumnHeader) {
- $skipColumnHeader = FALSE;
- continue;
- }
-
- /* trim whitespace around the values */
-
- $empty = TRUE;
- foreach ($values as $k => $v) {
- $values[$k] = trim($v, " \t\r\n");
- }
-
- if (CRM_Utils_System::isNull($values)) {
- continue;
- }
-
- $this->_totalCount++;
-
- if ($mode == self::MODE_MAPFIELD) {
- $returnCode = CRM_Import_Parser::VALID;
- }
- // Note that import summary appears to be unused
- elseif ($mode == self::MODE_PREVIEW || $mode == self::MODE_SUMMARY) {
- $returnCode = $this->summary($values);
- }
- elseif ($mode == self::MODE_IMPORT) {
- $returnCode = $this->import($onDuplicate, $values);
- if ($statusID && (($this->_lineCount % 50) == 0)) {
- $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount);
- }
- }
- else {
- $returnCode = self::ERROR;
- }
-
- // note that a line could be valid but still produce a warning
- if ($returnCode == self::VALID) {
- $this->_validCount++;
- if ($mode == self::MODE_MAPFIELD) {
- $this->_rows[] = $values;
- $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
- }
- }
-
- if ($returnCode == self::SOFT_CREDIT) {
- $this->_validSoftCreditRowCount++;
- $this->_validCount++;
- if ($mode == self::MODE_MAPFIELD) {
- $this->_rows[] = $values;
- $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
- }
- }
-
- if ($returnCode == self::PLEDGE_PAYMENT) {
- $this->_validPledgePaymentRowCount++;
- $this->_validCount++;
- if ($mode == self::MODE_MAPFIELD) {
- $this->_rows[] = $values;
- $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
- }
- }
-
- if ($returnCode == self::ERROR) {
- $this->_invalidRowCount++;
- $recordNumber = $this->_lineCount;
- if ($this->_haveColumnHeader) {
- $recordNumber--;
- }
- array_unshift($values, $recordNumber);
- $this->_errors[] = $values;
- }
-
- if ($returnCode == self::PLEDGE_PAYMENT_ERROR) {
- $this->_invalidPledgePaymentRowCount++;
- $recordNumber = $this->_lineCount;
- if ($this->_haveColumnHeader) {
- $recordNumber--;
- }
- array_unshift($values, $recordNumber);
- $this->_pledgePaymentErrors[] = $values;
- }
-
- if ($returnCode == self::SOFT_CREDIT_ERROR) {
- $this->_invalidSoftCreditRowCount++;
- $recordNumber = $this->_lineCount;
- if ($this->_haveColumnHeader) {
- $recordNumber--;
- }
- array_unshift($values, $recordNumber);
- $this->_softCreditErrors[] = $values;
- }
-
- if ($returnCode == self::DUPLICATE) {
- $this->_duplicateCount++;
- $recordNumber = $this->_lineCount;
- if ($this->_haveColumnHeader) {
- $recordNumber--;
- }
- array_unshift($values, $recordNumber);
- $this->_duplicates[] = $values;
- if ($onDuplicate != self::DUPLICATE_SKIP) {
- $this->_validCount++;
- }
- }
-
- // if we are done processing the maxNumber of lines, break
- if ($this->_maxLinesToProcess > 0 && $this->_validCount >= $this->_maxLinesToProcess) {
- break;
- }
- }
-
- fclose($fd);
-
- if ($mode == self::MODE_PREVIEW || $mode == self::MODE_IMPORT) {
- $customHeaders = $mapper;
-
- $customfields = CRM_Core_BAO_CustomField::getFields('Contribution');
- foreach ($customHeaders as $key => $value) {
- if ($id = CRM_Core_BAO_CustomField::getKeyID($value)) {
- $customHeaders[$key] = $customfields[$id][0];
- }
- }
- if ($this->_invalidRowCount) {
- // removed view url for invlaid contacts
- $headers = array_merge([
- ts('Line Number'),
- ts('Reason'),
- ], $customHeaders);
- $this->_errorFileName = self::errorFileName(self::ERROR);
- self::exportCSV($this->_errorFileName, $headers, $this->_errors);
- }
-
- if ($this->_invalidPledgePaymentRowCount) {
- // removed view url for invlaid contacts
- $headers = array_merge([
- ts('Line Number'),
- ts('Reason'),
- ], $customHeaders);
- $this->_pledgePaymentErrorsFileName = self::errorFileName(self::PLEDGE_PAYMENT_ERROR);
- self::exportCSV($this->_pledgePaymentErrorsFileName, $headers, $this->_pledgePaymentErrors);
- }
-
- if ($this->_invalidSoftCreditRowCount) {
- // removed view url for invlaid contacts
- $headers = array_merge([
- ts('Line Number'),
- ts('Reason'),
- ], $customHeaders);
- $this->_softCreditErrorsFileName = self::errorFileName(self::SOFT_CREDIT_ERROR);
- self::exportCSV($this->_softCreditErrorsFileName, $headers, $this->_softCreditErrors);
- }
-
- if ($this->_duplicateCount) {
- $headers = array_merge([
- ts('Line Number'),
- ts('View Contribution URL'),
- ], $customHeaders);
-
- $this->_duplicateFileName = self::errorFileName(self::DUPLICATE);
- self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates);
- }
- }
- }
-
- /**
- * Given a list of the importable field keys that the user has selected
- * set the active fields array to this list
+ * This is the same format as saved in civicrm_mapping_field except
+ * that location_type_id = 'Primary' rather than empty where relevant.
+ * Also 'im_provider_id' is mapped to the 'real' field name 'provider_id'
*
- * @param array $fieldKeys mapped array of values
- */
- public function setActiveFields($fieldKeys) {
- $this->_activeFieldCount = count($fieldKeys);
- foreach ($fieldKeys as $key) {
- if (empty($this->_fields[$key])) {
- $this->_activeFields[] = new CRM_Contribute_Import_Field('', ts('- do not import -'));
- }
- else {
- $this->_activeFields[] = clone($this->_fields[$key]);
- }
- }
- }
-
- /**
- * Store the soft credit field information.
- *
- * This was perhaps done this way on the believe that a lot of code pain
- * was worth it to avoid negligible-cost array iterations. Perhaps we could prioritise
- * readability & maintainability next since we can just work with functions to retrieve
- * data from the metadata.
- *
- * @param array $elements
- */
- public function setActiveFieldSoftCredit($elements) {
- foreach ((array) $elements as $i => $element) {
- $this->_activeFields[$i]->_softCreditField = $element;
- }
- }
-
- /**
- * Store the soft credit field type information.
- *
- * This was perhaps done this way on the believe that a lot of code pain
- * was worth it to avoid negligible-cost array iterations. Perhaps we could prioritise
- * readability & maintainability next since we can just work with functions to retrieve
- * data from the metadata.
- *
- * @param array $elements
+ * @return array
+ * @throws \API_Exception
*/
- public function setActiveFieldSoftCreditType($elements) {
- foreach ((array) $elements as $i => $element) {
- $this->_activeFields[$i]->_softCreditType = $element;
+ protected function getFieldMappings(): array {
+ $mappedFields = [];
+ foreach ($this->getSubmittedValue('mapper') as $i => $mapperRow) {
+ $mappedField = $this->getMappingFieldFromMapperInput($mapperRow, 0, $i);
+ // Just for clarity since 0 is a pseudo-value
+ unset($mappedField['mapping_id']);
+ $mappedFields[] = $mappedField;
}
+ return $mappedFields;
}
/**
- * Format the field values for input to the api.
+ * Get the required fields.
*
* @return array
- * (reference ) associative array of name/value pairs
*/
- public function &getActiveFieldParams() {
- $params = [];
- for ($i = 0; $i < $this->_activeFieldCount; $i++) {
- if (isset($this->_activeFields[$i]->_value)) {
- if (isset($this->_activeFields[$i]->_softCreditField)) {
- if (!isset($params[$this->_activeFields[$i]->_name])) {
- $params[$this->_activeFields[$i]->_name] = [];
- }
- $params[$this->_activeFields[$i]->_name][$i][$this->_activeFields[$i]->_softCreditField] = $this->_activeFields[$i]->_value;
- if (isset($this->_activeFields[$i]->_softCreditType)) {
- $params[$this->_activeFields[$i]->_name][$i]['soft_credit_type_id'] = $this->_activeFields[$i]->_softCreditType;
- }
- }
-
- if (!isset($params[$this->_activeFields[$i]->_name])) {
- if (!isset($this->_activeFields[$i]->_softCreditField)) {
- $params[$this->_activeFields[$i]->_name] = $this->_activeFields[$i]->_value;
- }
- }
- }
- }
- return $params;
+ public function getRequiredFields(): array {
+ return ['id' => ts('Contribution ID'), ['financial_type_id' => ts('Financial Type'), 'total_amount' => ts('Total Amount')]];
}
/**
public function getMappedRow(array $values): array {
$params = [];
foreach ($this->getFieldMappings() as $i => $mappedField) {
+ if ($mappedField['name'] === 'do_not_import' || !$mappedField['name']) {
+ continue;
+ }
if (!empty($mappedField['soft_credit_match_field'])) {
$params['soft_credit'][$i] = ['soft_credit_type_id' => $mappedField['soft_credit_type_id'], $mappedField['soft_credit_match_field'] => $values[$i]];
}
else {
- $params[$this->getFieldMetadata($mappedField['name'])['name']] = $values[$i];
+ $params[$this->getFieldMetadata($mappedField['name'])['name']] = $this->getTransformedFieldValue($mappedField['name'], $values[$i]);
}
}
return $params;
}
}
- /**
- * Store parser values.
- *
- * @param CRM_Core_Session $store
- *
- * @param int $mode
- */
- public function set($store, $mode = self::MODE_SUMMARY) {
- $store->set('fileSize', $this->_fileSize);
- $store->set('lineCount', $this->_lineCount);
- $store->set('separator', $this->_separator);
- $store->set('fields', $this->getSelectValues());
-
- $store->set('headerPatterns', $this->getHeaderPatterns());
- $store->set('dataPatterns', $this->getDataPatterns());
- $store->set('columnCount', $this->_activeFieldCount);
-
- $store->set('totalRowCount', $this->_totalCount);
- $store->set('validRowCount', $this->_validCount);
- $store->set('invalidRowCount', $this->_invalidRowCount);
- $store->set('invalidSoftCreditRowCount', $this->_invalidSoftCreditRowCount);
- $store->set('validSoftCreditRowCount', $this->_validSoftCreditRowCount);
- $store->set('invalidPledgePaymentRowCount', $this->_invalidPledgePaymentRowCount);
- $store->set('validPledgePaymentRowCount', $this->_validPledgePaymentRowCount);
-
- switch ($this->_contactType) {
- case 'Individual':
- $store->set('contactType', CRM_Import_Parser::CONTACT_INDIVIDUAL);
- break;
-
- case 'Household':
- $store->set('contactType', CRM_Import_Parser::CONTACT_HOUSEHOLD);
- break;
-
- case 'Organization':
- $store->set('contactType', CRM_Import_Parser::CONTACT_ORGANIZATION);
- }
-
- if ($this->_invalidRowCount) {
- $store->set('errorsFileName', $this->_errorFileName);
- }
- if (isset($this->_rows) && !empty($this->_rows)) {
- $store->set('dataValues', $this->_rows);
- }
-
- if ($this->_invalidPledgePaymentRowCount) {
- $store->set('pledgePaymentErrorsFileName', $this->_pledgePaymentErrorsFileName);
- }
-
- if ($this->_invalidSoftCreditRowCount) {
- $store->set('softCreditErrorsFileName', $this->_softCreditErrorsFileName);
- }
-
- if ($mode == self::MODE_IMPORT) {
- $store->set('duplicateRowCount', $this->_duplicateCount);
- if ($this->_duplicateCount) {
- $store->set('duplicatesFileName', $this->_duplicateFileName);
- }
- }
- }
-
- /**
- * Export data to a CSV file.
- *
- * @param string $fileName
- * @param array $header
- * @param array $data
- */
- public static function exportCSV($fileName, $header, $data) {
- $output = [];
- $fd = fopen($fileName, 'w');
-
- foreach ($header as $key => $value) {
- $header[$key] = "\"$value\"";
- }
- $config = CRM_Core_Config::singleton();
- $output[] = implode($config->fieldSeparator, $header);
-
- foreach ($data as $datum) {
- foreach ($datum as $key => $value) {
- if (isset($value[0]) && is_array($value)) {
- foreach ($value[0] as $k1 => $v1) {
- if ($k1 == 'location_type_id') {
- continue;
- }
- $datum[$k1] = $v1;
- }
- }
- else {
- $datum[$key] = "\"$value\"";
- }
- }
- $output[] = implode($config->fieldSeparator, $datum);
- }
- fwrite($fd, implode("\n", $output));
- fclose($fd);
- }
-
- /**
- * Determines the file extension based on error code.
- *
- * @param int $type
- * Error code constant.
- *
- * @return string
- */
- public static function errorFileName($type) {
- $fileName = NULL;
- if (empty($type)) {
- return $fileName;
- }
-
- $config = CRM_Core_Config::singleton();
- $fileName = $config->uploadDir . "sqlImport";
-
- switch ($type) {
- case self::SOFT_CREDIT_ERROR:
- $fileName .= '.softCreditErrors';
- break;
-
- case self::PLEDGE_PAYMENT_ERROR:
- $fileName .= '.pledgePaymentErrors';
- break;
-
- default:
- $fileName = parent::errorFileName($type);
- break;
- }
-
- return $fileName;
- }
-
- /**
- * Determines the file name based on error code.
- *
- * @param int $type
- * Error code constant.
- *
- * @return string
- */
- public static function saveFileName($type) {
- $fileName = NULL;
- if (empty($type)) {
- return $fileName;
- }
-
- switch ($type) {
- case self::SOFT_CREDIT_ERROR:
- $fileName = 'Import_Soft_Credit_Errors.csv';
- break;
-
- case self::PLEDGE_PAYMENT_ERROR:
- $fileName = 'Import_Pledge_Payment_Errors.csv';
- break;
-
- default:
- $fileName = parent::saveFileName($type);
- break;
- }
-
- return $fileName;
- }
-
/**
* The initializer code, called before the processing
*/
public function init() {
+ // Force re-load of user job.
+ unset($this->userJob);
$this->setFieldMetadata();
foreach ($this->getImportableFieldsMetadata() as $name => $field) {
$this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
}
-
- $this->_newContributions = [];
-
- $this->setActiveFields($this->_mapperKeys);
- $this->setActiveFieldSoftCredit($this->_mapperSoftCredit);
- $this->setActiveFieldSoftCreditType($this->_mapperSoftCreditType);
-
- // FIXME: we should do this in one place together with Form/MapField.php
- $this->_contactIdIndex = -1;
-
- $index = 0;
- foreach ($this->_mapperKeys as $key) {
- switch ($key) {
- case 'contribution_contact_id':
- $this->_contactIdIndex = $index;
- break;
-
- }
- $index++;
- }
}
/**
*/
protected function setFieldMetadata() {
if (empty($this->importableFieldsMetadata)) {
- $fields = CRM_Contribute_BAO_Contribution::importableFields($this->_contactType, FALSE);
+ $fields = CRM_Contribute_BAO_Contribution::importableFields($this->getContactType(), FALSE);
$fields = array_merge($fields,
[
'title' => ts('Soft Credit'),
'softCredit' => TRUE,
'headerPattern' => '/Soft Credit/i',
+ 'options' => FALSE,
+ 'type' => CRM_Utils_Type::T_STRING,
],
]
);
// add pledge fields only if its is enabled
if (CRM_Core_Permission::access('CiviPledge')) {
$pledgeFields = [
- 'pledge_payment' => [
- 'title' => ts('Pledge Payment'),
- 'headerPattern' => '/Pledge Payment/i',
- ],
'pledge_id' => [
'title' => ts('Pledge ID'),
'headerPattern' => '/Pledge ID/i',
+ 'name' => 'pledge_id',
+ 'entity' => 'Pledge',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'options' => FALSE,
],
];
}
/**
- * Handle the values in summary mode.
+ * Handle the values in import mode.
*
* @param array $values
* The array of values belonging to this line.
- *
- * @return int
- * CRM_Import_Parser::VALID or CRM_Import_Parser::ERROR
*/
- public function summary(&$values) {
- $this->setActiveFieldValues($values);
-
- $params = $this->getActiveFieldParams();
-
- //for date-Formats
- $errorMessage = implode('; ', $this->formatDateFields($params));
- //date-Format part ends
+ public function import($values): void {
+ $rowNumber = (int) ($values[array_key_last($values)]);
+ try {
+ $params = $this->getMappedRow($values);
+ $formatted = array_merge(['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => TRUE, 'contribution_id' => $params['id'] ?? NULL], $params);
+ //CRM-10994
+ if (isset($params['total_amount']) && $params['total_amount'] == 0) {
+ $params['total_amount'] = '0.00';
+ }
+ $this->formatInput($params, $formatted);
+
+ $paramValues = [];
+ foreach ($params as $key => $field) {
+ if ($field == NULL || $field === '') {
+ continue;
+ }
+ $paramValues[$key] = $field;
+ }
- $params['contact_type'] = 'Contribution';
+ //import contribution record according to select contact type
+ if ($this->isSkipDuplicates() &&
+ (!empty($paramValues['contribution_contact_id']) || !empty($paramValues['external_identifier']))
+ ) {
+ $paramValues['contact_type'] = $this->getContactType();
+ }
+ elseif ($this->isUpdateExisting() &&
+ (!empty($paramValues['contribution_id']) || !empty($values['trxn_id']) || !empty($paramValues['invoice_id']))
+ ) {
+ $paramValues['contact_type'] = $this->getContactType();
+ }
+ elseif (!empty($paramValues['pledge_payment'])) {
+ $paramValues['contact_type'] = $this->getContactType();
+ }
- //checking error in custom data
- CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
+ $formatError = $this->deprecatedFormatParams($paramValues, $formatted);
- if ($errorMessage) {
- $tempMsg = "Invalid value for field(s) : $errorMessage";
- array_unshift($values, $tempMsg);
- $errorMessage = NULL;
- return CRM_Import_Parser::ERROR;
- }
+ if ($formatError) {
+ if (CRM_Utils_Array::value('error_data', $formatError) == 'soft_credit') {
+ throw new CRM_Core_Exception('', self::SOFT_CREDIT_ERROR);
+ }
+ if (CRM_Utils_Array::value('error_data', $formatError) == 'pledge_payment') {
+ throw new CRM_Core_Exception('', self::PLEDGE_PAYMENT_ERROR);
+ }
+ throw new CRM_Core_Exception('', CRM_Import_Parser::ERROR);
+ }
+
+ if ($this->isUpdateExisting()) {
+ //fix for CRM-2219 - Update Contribution
+ // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE
+ if (!empty($paramValues['invoice_id']) || !empty($paramValues['trxn_id']) || !empty($paramValues['contribution_id'])) {
+ $dupeIds = [
+ 'id' => $paramValues['contribution_id'] ?? NULL,
+ 'trxn_id' => $paramValues['trxn_id'] ?? NULL,
+ 'invoice_id' => $paramValues['invoice_id'] ?? NULL,
+ ];
+ $ids['contribution'] = CRM_Contribute_BAO_Contribution::checkDuplicateIds($dupeIds);
+
+ if ($ids['contribution']) {
+ $formatted['id'] = $ids['contribution'];
+ //process note
+ if (!empty($paramValues['note'])) {
+ $noteID = [];
+ $contactID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id');
+ $daoNote = new CRM_Core_BAO_Note();
+ $daoNote->entity_table = 'civicrm_contribution';
+ $daoNote->entity_id = $ids['contribution'];
+ if ($daoNote->find(TRUE)) {
+ $noteID['id'] = $daoNote->id;
+ }
- return CRM_Import_Parser::VALID;
- }
+ $noteParams = [
+ 'entity_table' => 'civicrm_contribution',
+ 'note' => $paramValues['note'],
+ 'entity_id' => $ids['contribution'],
+ 'contact_id' => $contactID,
+ ];
+ CRM_Core_BAO_Note::add($noteParams, $noteID);
+ unset($formatted['note']);
+ }
+
+ //need to check existing soft credit contribution, CRM-3968
+ if (!empty($formatted['soft_credit'])) {
+ $dupeSoftCredit = [
+ 'contact_id' => $formatted['soft_credit'],
+ 'contribution_id' => $ids['contribution'],
+ ];
+
+ //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null
+ $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($dupeSoftCredit['contribution_id']);
+ if (isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])) {
+ foreach ($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues) {
+ if (!empty($existingSoftCreditValues['soft_credit_id'])) {
+ civicrm_api3('ContributionSoft', 'delete', [
+ 'id' => $existingSoftCreditValues['soft_credit_id'],
+ 'pcp_id' => NULL,
+ ]);
+ }
+ }
+ }
+ }
- /**
- * Handle the values in import mode.
- *
- * @param int $onDuplicate
- * The code for what action to take on duplicates.
- * @param array $values
- * The array of values belonging to this line.
- *
- * @return int
- * the result of this processing - one of
- * - CRM_Import_Parser::VALID
- * - CRM_Import_Parser::ERROR
- * - CRM_Import_Parser::SOFT_CREDIT_ERROR
- * - CRM_Import_Parser::PLEDGE_PAYMENT_ERROR
- * - CRM_Import_Parser::DUPLICATE
- * - CRM_Import_Parser::SOFT_CREDIT (successful creation)
- * - CRM_Import_Parser::PLEDGE_PAYMENT (successful creation)
- */
- public function import($onDuplicate, &$values) {
- // first make sure this is a valid line
- $response = $this->summary($values);
- if ($response != CRM_Import_Parser::VALID) {
- return CRM_Import_Parser::ERROR;
- }
+ $formatted['id'] = $ids['contribution'];
- $params = &$this->getActiveFieldParams();
- $formatted = ['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE];
+ $newContribution = civicrm_api3('contribution', 'create', $formatted);
+ $this->_newContributions[] = $newContribution['id'];
- //CRM-10994
- if (isset($params['total_amount']) && $params['total_amount'] == 0) {
- $params['total_amount'] = '0.00';
- }
- $this->formatInput($params, $formatted);
+ //return soft valid since we need to show how soft credits were added
+ if (!empty($formatted['soft_credit'])) {
+ $this->setImportStatus($rowNumber, $this->getStatus(self::SOFT_CREDIT));
+ return;
+ }
- $paramValues = [];
- foreach ($params as $key => $field) {
- if ($field == NULL || $field === '') {
- continue;
+ // process pledge payment assoc w/ the contribution
+ $this->processPledgePayments($formatted);
+ $this->setImportStatus($rowNumber, $this->getStatus(self::PLEDGE_PAYMENT));
+ return;
+ }
+ $labels = [
+ 'id' => 'Contribution ID',
+ 'trxn_id' => 'Transaction ID',
+ 'invoice_id' => 'Invoice ID',
+ ];
+ foreach ($dupeIds as $k => $v) {
+ if ($v) {
+ $errorMsg[] = "$labels[$k] $v";
+ }
+ }
+ $errorMsg = implode(' AND ', $errorMsg);
+ throw new CRM_Core_Exception('Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.', CRM_Import_Parser::ERROR);
+ }
}
- $paramValues[$key] = $field;
- }
- //import contribution record according to select contact type
- if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP &&
- (!empty($paramValues['contribution_contact_id']) || !empty($paramValues['external_identifier']))
- ) {
- $paramValues['contact_type'] = $this->_contactType;
- }
- elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE &&
- (!empty($paramValues['contribution_id']) || !empty($values['trxn_id']) || !empty($paramValues['invoice_id']))
- ) {
- $paramValues['contact_type'] = $this->_contactType;
- }
- elseif (!empty($params['soft_credit'])) {
- $paramValues['contact_type'] = $this->_contactType;
- }
- elseif (!empty($paramValues['pledge_payment'])) {
- $paramValues['contact_type'] = $this->_contactType;
- }
+ if (empty($formatted['contact_id'])) {
- //need to pass $onDuplicate to check import mode.
- if (!empty($paramValues['pledge_payment'])) {
- $paramValues['onDuplicate'] = $onDuplicate;
- }
- $formatError = $this->deprecatedFormatParams($paramValues, $formatted, TRUE, $onDuplicate);
-
- if ($formatError) {
- array_unshift($values, $formatError['error_message']);
- if (CRM_Utils_Array::value('error_data', $formatError) == 'soft_credit') {
- return self::SOFT_CREDIT_ERROR;
- }
- if (CRM_Utils_Array::value('error_data', $formatError) == 'pledge_payment') {
- return self::PLEDGE_PAYMENT_ERROR;
- }
- return CRM_Import_Parser::ERROR;
- }
-
- if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
- //fix for CRM-2219 - Update Contribution
- // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE
- if (!empty($paramValues['invoice_id']) || !empty($paramValues['trxn_id']) || !empty($paramValues['contribution_id'])) {
- $dupeIds = [
- 'id' => $paramValues['contribution_id'] ?? NULL,
- 'trxn_id' => $paramValues['trxn_id'] ?? NULL,
- 'invoice_id' => $paramValues['invoice_id'] ?? NULL,
- ];
- $ids['contribution'] = CRM_Contribute_BAO_Contribution::checkDuplicateIds($dupeIds);
-
- if ($ids['contribution']) {
- $formatted['id'] = $ids['contribution'];
- //process note
- if (!empty($paramValues['note'])) {
- $noteID = [];
- $contactID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id');
- $daoNote = new CRM_Core_BAO_Note();
- $daoNote->entity_table = 'civicrm_contribution';
- $daoNote->entity_id = $ids['contribution'];
- if ($daoNote->find(TRUE)) {
- $noteID['id'] = $daoNote->id;
- }
+ $error = $this->checkContactDuplicate($paramValues);
- $noteParams = [
- 'entity_table' => 'civicrm_contribution',
- 'note' => $paramValues['note'],
- 'entity_id' => $ids['contribution'],
- 'contact_id' => $contactID,
- ];
- CRM_Core_BAO_Note::add($noteParams, $noteID);
- unset($formatted['note']);
+ if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
+ $matchedIDs = explode(',', $error['error_message']['params'][0]);
+ if (count($matchedIDs) > 1) {
+ throw new CRM_Core_Exception('Multiple matching contact records detected for this row. The contribution was not imported', CRM_Import_Parser::ERROR);
}
+ $cid = $matchedIDs[0];
+ $formatted['contact_id'] = $cid;
- //need to check existing soft credit contribution, CRM-3968
- if (!empty($formatted['soft_credit'])) {
- $dupeSoftCredit = [
- 'contact_id' => $formatted['soft_credit'],
- 'contribution_id' => $ids['contribution'],
- ];
-
- //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null
- $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($dupeSoftCredit['contribution_id']);
- if (isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])) {
- foreach ($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues) {
- if (!empty($existingSoftCreditValues['soft_credit_id'])) {
- civicrm_api3('ContributionSoft', 'delete', [
- 'id' => $existingSoftCreditValues['soft_credit_id'],
- 'pcp_id' => NULL,
- ]);
- }
+ $newContribution = civicrm_api('contribution', 'create', $formatted);
+ if (civicrm_error($newContribution)) {
+ if (is_array($newContribution['error_message'])) {
+ if ($newContribution['error_message']['params'][0]) {
+ throw new CRM_Core_Exception($newContribution['error_message']['message'], CRM_Import_Parser::DUPLICATE);
}
}
+ else {
+ throw new CRM_Core_Exception($newContribution['error_message'], CRM_Import_Parser::ERROR);
+ }
}
- $formatted['id'] = $ids['contribution'];
-
- $newContribution = civicrm_api3('contribution', 'create', $formatted);
$this->_newContributions[] = $newContribution['id'];
+ $formatted['contribution_id'] = $newContribution['id'];
//return soft valid since we need to show how soft credits were added
if (!empty($formatted['soft_credit'])) {
- return self::SOFT_CREDIT;
+ $this->setImportStatus($rowNumber, $this->getStatus(self::SOFT_CREDIT));
+ return;
}
- // process pledge payment assoc w/ the contribution
- return $this->processPledgePayments($formatted);
+ $this->processPledgePayments($formatted);
+ $this->setImportStatus($rowNumber, $this->getStatus(self::PLEDGE_PAYMENT));
+ return;
}
- $labels = [
- 'id' => 'Contribution ID',
- 'trxn_id' => 'Transaction ID',
- 'invoice_id' => 'Invoice ID',
+
+ // Using new Dedupe rule.
+ $ruleParams = [
+ 'contact_type' => $this->getContactType(),
+ 'used' => 'Unsupervised',
];
- foreach ($dupeIds as $k => $v) {
- if ($v) {
- $errorMsg[] = "$labels[$k] $v";
+ $fieldsArray = CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams);
+ $disp = NULL;
+ foreach ($fieldsArray as $value) {
+ if (array_key_exists(trim($value), $params)) {
+ $paramValue = $params[trim($value)];
+ if (is_array($paramValue)) {
+ $disp .= $params[trim($value)][0][trim($value)] . " ";
+ }
+ else {
+ $disp .= $params[trim($value)] . " ";
+ }
}
}
- $errorMsg = implode(' AND ', $errorMsg);
- array_unshift($values, 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.');
- return CRM_Import_Parser::ERROR;
- }
- }
- if ($this->_contactIdIndex < 0) {
-
- $error = $this->checkContactDuplicate($paramValues);
-
- if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
- $matchedIDs = explode(',', $error['error_message']['params'][0]);
- if (count($matchedIDs) > 1) {
- array_unshift($values, 'Multiple matching contact records detected for this row. The contribution was not imported');
- return CRM_Import_Parser::ERROR;
- }
- $cid = $matchedIDs[0];
- $formatted['contact_id'] = $cid;
-
- $newContribution = civicrm_api('contribution', 'create', $formatted);
- if (civicrm_error($newContribution)) {
- if (is_array($newContribution['error_message'])) {
- array_unshift($values, $newContribution['error_message']['message']);
- if ($newContribution['error_message']['params'][0]) {
- return CRM_Import_Parser::DUPLICATE;
- }
+ if (!empty($params['external_identifier'])) {
+ if ($disp) {
+ $disp .= "AND {$params['external_identifier']}";
}
else {
- array_unshift($values, $newContribution['error_message']);
- return CRM_Import_Parser::ERROR;
+ $disp = $params['external_identifier'];
}
}
-
- $this->_newContributions[] = $newContribution['id'];
- $formatted['contribution_id'] = $newContribution['id'];
-
- //return soft valid since we need to show how soft credits were added
- if (!empty($formatted['soft_credit'])) {
- return self::SOFT_CREDIT;
- }
-
- // process pledge payment assoc w/ the contribution
- return $this->processPledgePayments($formatted);
+ $errorMessage = 'No matching Contact found for (' . $disp . ')';
+ throw new CRM_Core_Exception($errorMessage, CRM_Import_Parser::ERROR);
}
- // Using new Dedupe rule.
- $ruleParams = [
- 'contact_type' => $this->_contactType,
- 'used' => 'Unsupervised',
- ];
- $fieldsArray = CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams);
- $disp = NULL;
- foreach ($fieldsArray as $value) {
- if (array_key_exists(trim($value), $params)) {
- $paramValue = $params[trim($value)];
- if (is_array($paramValue)) {
- $disp .= $params[trim($value)][0][trim($value)] . " ";
- }
- else {
- $disp .= $params[trim($value)] . " ";
- }
+ if (!empty($paramValues['external_identifier'])) {
+ $checkCid = new CRM_Contact_DAO_Contact();
+ $checkCid->external_identifier = $paramValues['external_identifier'];
+ $checkCid->find(TRUE);
+ if ($checkCid->id != $formatted['contact_id']) {
+ $errorMessage = 'Mismatch of External ID:' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id'];
+ throw new CRM_Core_Exception($errorMessage, CRM_Import_Parser::ERROR);
}
}
-
- if (!empty($params['external_identifier'])) {
- if ($disp) {
- $disp .= "AND {$params['external_identifier']}";
+ $newContribution = civicrm_api('contribution', 'create', $formatted);
+ if (civicrm_error($newContribution)) {
+ if (is_array($newContribution['error_message'])) {
+ if ($newContribution['error_message']['params'][0]) {
+ throw new CRM_Core_Exception('', CRM_Import_Parser::DUPLICATE);
+ }
}
else {
- $disp = $params['external_identifier'];
+ throw new CRM_Core_Exception($newContribution['error_message'], CRM_Import_Parser::ERROR);
}
}
- array_unshift($values, 'No matching Contact found for (' . $disp . ')');
- return CRM_Import_Parser::ERROR;
- }
+ $this->_newContributions[] = $newContribution['id'];
+ $formatted['contribution_id'] = $newContribution['id'];
- if (!empty($paramValues['external_identifier'])) {
- $checkCid = new CRM_Contact_DAO_Contact();
- $checkCid->external_identifier = $paramValues['external_identifier'];
- $checkCid->find(TRUE);
- if ($checkCid->id != $formatted['contact_id']) {
- array_unshift($values, 'Mismatch of External ID:' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']);
- return CRM_Import_Parser::ERROR;
+ //return soft valid since we need to show how soft credits were added
+ if (!empty($formatted['soft_credit'])) {
+ $this->setImportStatus($rowNumber, $this->getStatus(self::SOFT_CREDIT), '');
+ return;
}
- }
- $newContribution = civicrm_api('contribution', 'create', $formatted);
- if (civicrm_error($newContribution)) {
- if (is_array($newContribution['error_message'])) {
- array_unshift($values, $newContribution['error_message']['message']);
- if ($newContribution['error_message']['params'][0]) {
- return CRM_Import_Parser::DUPLICATE;
- }
- }
- else {
- array_unshift($values, $newContribution['error_message']);
- return CRM_Import_Parser::ERROR;
- }
- }
- $this->_newContributions[] = $newContribution['id'];
- $formatted['contribution_id'] = $newContribution['id'];
+ // process pledge payment assoc w/ the contribution
+ $this->processPledgePayments($formatted);
+ $this->setImportStatus($rowNumber, $this->getStatus(self::PLEDGE_PAYMENT));
+ return;
- //return soft valid since we need to show how soft credits were added
- if (!empty($formatted['soft_credit'])) {
- return self::SOFT_CREDIT;
}
+ catch (CRM_Core_Exception $e) {
+ $this->setImportStatus($rowNumber, $this->getStatus($e->getErrorCode()), $e->getMessage());
+ }
+ }
- // process pledge payment assoc w/ the contribution
- return $this->processPledgePayments($formatted);
+ /**
+ * Get the status to record.
+ *
+ * @param int|null $code
+ *
+ * @return string
+ */
+ protected function getStatus(?int $code): string {
+ $errorMapping = [
+ self::SOFT_CREDIT_ERROR => 'soft_credit_error',
+ self::PLEDGE_PAYMENT_ERROR => 'pledge_payment_error',
+ self::SOFT_CREDIT => 'soft_credit_imported',
+ self::PLEDGE_PAYMENT => 'pledge_payment_imported',
+ CRM_Import_Parser::DUPLICATE => 'DUPLICATE',
+ CRM_Import_Parser::VALID => 'IMPORTED',
+ ];
+ return $errorMapping[$code] ?? 'ERROR';
}
/**
* Process pledge payments.
*
* @param array $formatted
- *
- * @return int
*/
private function processPledgePayments(array $formatted) {
if (!empty($formatted['pledge_payment_id']) && !empty($formatted['pledge_id'])) {
NULL,
$formatted['total_amount']
);
-
- return self::PLEDGE_PAYMENT;
}
}
return $this->_newContributions;
}
- /**
- * Format date fields from input to mysql.
- *
- * @param array $params
- *
- * @return array
- * Error messages, if any.
- */
- public function formatDateFields(&$params) {
- $errorMessage = [];
- $dateType = CRM_Core_Session::singleton()->get('dateTypes');
- foreach ($params as $key => $val) {
- if ($val) {
- switch ($key) {
- case 'receive_date':
- if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
- $params[$key] = $dateValue;
- }
- else {
- $errorMessage[] = ts('Receive Date');
- }
- break;
-
- case 'cancel_date':
- if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
- $params[$key] = $dateValue;
- }
- else {
- $errorMessage[] = ts('Cancel Date');
- }
- break;
-
- case 'receipt_date':
- if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
- $params[$key] = $dateValue;
- }
- else {
- $errorMessage[] = ts('Receipt date');
- }
- break;
-
- case 'thankyou_date':
- if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
- $params[$key] = $dateValue;
- }
- else {
- $errorMessage[] = ts('Thankyou Date');
- }
- break;
- }
- }
- }
- return $errorMessage;
- }
-
/**
* Format input params to suit api handling.
*
* @param array $formatted
*/
public function formatInput(&$params, &$formatted = []) {
- $dateType = CRM_Core_Session::singleton()->get('dateTypes');
- $customDataType = !empty($params['contact_type']) ? $params['contact_type'] : 'Contribution';
- $customFields = CRM_Core_BAO_CustomField::getFields($customDataType);
- // @todo call formatDateFields & move custom data handling there.
- // Also note error handling for dates is currently in deprecatedFormatParams
- // we should use the error handling in formatDateFields.
foreach ($params as $key => $val) {
// @todo - call formatDateFields instead.
if ($val) {
switch ($key) {
- case 'receive_date':
- case 'cancel_date':
- case 'receipt_date':
- case 'thankyou_date':
- $params[$key] = CRM_Utils_Date::formatDate($params[$key], $dateType);
- break;
case 'pledge_payment':
$params[$key] = CRM_Utils_String::strtobool($val);
break;
}
- if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
- if ($customFields[$customFieldID]['data_type'] == 'Date') {
- CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key);
- unset($params[$key]);
- }
- elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
- $params[$key] = CRM_Utils_String::strtoboolstr($val);
- }
- }
}
}
}
* @param array $values
* The reformatted properties that we can use internally.
* @param bool $create
- * @param int $onDuplicate
*
* @return array|CRM_Error
+ * @throws \CRM_Core_Exception
*/
- private function deprecatedFormatParams($params, &$values, $create = FALSE, $onDuplicate = NULL) {
+ private function deprecatedFormatParams($params, &$values, $create = FALSE) {
require_once 'CRM/Utils/DeprecatedUtils.php';
// copy all the contribution fields as is
require_once 'api/v3/utils.php';
- $fields = CRM_Core_DAO::getExportableFieldsWithPseudoConstants('CRM_Contribute_BAO_Contribution');
-
- _civicrm_api3_store_values($fields, $params, $values);
-
- $customFields = CRM_Core_BAO_CustomField::getFields('Contribution', FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE);
foreach ($params as $key => $value) {
// ignore empty values or empty arrays etc
continue;
}
- // Handling Custom Data
- if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
- $values[$key] = $value;
- $type = $customFields[$customFieldID]['html_type'];
- if (CRM_Core_BAO_CustomField::isSerialized($customFields[$customFieldID])) {
- $values[$key] = self::unserializeCustomValue($customFieldID, $value, $type);
- }
- elseif ($type == 'Select' || $type == 'Radio' ||
- ($type == 'Autocomplete-Select' &&
- $customFields[$customFieldID]['data_type'] == 'String'
- )
- ) {
- $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
- foreach ($customOption as $customFldID => $customValue) {
- $val = $customValue['value'] ?? NULL;
- $label = $customValue['label'] ?? NULL;
- $label = strtolower($label);
- $value = strtolower(trim($value));
- if (($value == $label) || ($value == strtolower($val))) {
- $values[$key] = $val;
- }
- }
- }
- continue;
- }
-
switch ($key) {
- case 'contribution_contact_id':
+ case 'contact_id':
if (!CRM_Utils_Rule::integer($value)) {
return civicrm_api3_create_error("contact_id not valid: $value");
}
elseif ($svq == 1) {
return civicrm_api3_create_error("Invalid Contact ID: contact_id $value is a soft-deleted contact.");
}
-
- $values['contact_id'] = $values['contribution_contact_id'];
- unset($values['contribution_contact_id']);
+ $values['contact_id'] = $value;
break;
case 'contact_type':
}
}
else {
- if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
+ if ($this->isUpdateExisting()) {
return civicrm_api3_create_error("Empty Contribution and Invoice and Transaction ID. Row was skipped.");
}
}
break;
- case 'receive_date':
- case 'cancel_date':
- case 'receipt_date':
- case 'thankyou_date':
- if (!CRM_Utils_Rule::dateTime($value)) {
- return civicrm_api3_create_error("$key not a valid date: $value");
- }
- break;
-
- case 'non_deductible_amount':
- case 'total_amount':
- case 'fee_amount':
- case 'net_amount':
- // @todo add test like testPaymentTypeLabel & remove these lines as we can anticipate error will still be caught & handled.
- if (!CRM_Utils_Rule::money($value)) {
- return civicrm_api3_create_error("$key not a valid amount: $value");
- }
- break;
-
- case 'currency':
- if (!CRM_Utils_Rule::currencyCode($value)) {
- return civicrm_api3_create_error("currency not a valid code: $value");
- }
- break;
-
case 'soft_credit':
// import contribution record according to select contact type
// validate contact id and external identifier.
- $value[$key] = $mismatchContactType = $softCreditContactIds = '';
- if (isset($params[$key]) && is_array($params[$key])) {
- foreach ($params[$key] as $softKey => $softParam) {
- $contactId = $softParam['contact_id'] ?? NULL;
- $externalId = $softParam['external_identifier'] ?? NULL;
- $email = $softParam['email'] ?? NULL;
- if ($contactId || $externalId) {
- require_once 'CRM/Contact/DAO/Contact.php';
- $contact = new CRM_Contact_DAO_Contact();
- $contact->id = $contactId;
- $contact->external_identifier = $externalId;
- $errorMsg = NULL;
- if (!$contact->find(TRUE)) {
- $field = $contactId ? ts('Contact ID') : ts('External ID');
- $errorMsg = ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.",
- [1 => $field, 2 => $contactId ? $contactId : $externalId]);
- }
-
- if ($errorMsg) {
- return civicrm_api3_create_error($errorMsg);
- }
-
- // finally get soft credit contact id.
- $values[$key][$softKey] = $softParam;
- $values[$key][$softKey]['contact_id'] = $contact->id;
- }
- elseif ($email) {
- if (!CRM_Utils_Rule::email($email)) {
- return civicrm_api3_create_error("Invalid email address $email provided for Soft Credit. Row was skipped");
- }
-
- // get the contact id from duplicate contact rule, if more than one contact is returned
- // we should return error, since current interface allows only one-one mapping
- $emailParams = [
- 'email' => $email,
- 'contact_type' => $params['contact_type'],
- ];
- $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams);
- if (!$checkDedupe['is_error']) {
- return civicrm_api3_create_error("Invalid email address(doesn't exist) $email for Soft Credit. Row was skipped");
- }
- $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]);
- if (count($matchingContactIds) > 1) {
- return civicrm_api3_create_error("Invalid email address(duplicate) $email for Soft Credit. Row was skipped");
- }
- if (count($matchingContactIds) == 1) {
- $contactId = $matchingContactIds[0];
- unset($softParam['email']);
- $values[$key][$softKey] = $softParam + ['contact_id' => $contactId];
- }
- }
- }
+ foreach ($value as $softKey => $softParam) {
+ $values['soft_credit'][$softKey] = [
+ 'contact_id' => $this->lookupMatchingContact($softParam),
+ 'soft_credit_type_id' => $softParam['soft_credit_type_id'],
+ ];
}
break;
- case 'pledge_payment':
case 'pledge_id':
-
- // giving respect to pledge_payment flag.
- if (empty($params['pledge_payment'])) {
- break;
- }
-
// get total amount of from import fields
$totalAmount = $params['total_amount'] ?? NULL;
-
- $onDuplicate = $params['onDuplicate'] ?? NULL;
-
// we need to get contact id $contributionContactID to
// retrieve pledge details as well as to validate pledge ID
// first need to check for update mode
- if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE &&
- ($params['contribution_id'] || $params['trxn_id'] || $params['invoice_id'])
+ if ($this->isUpdateExisting() &&
+ ($params['id'] || $params['trxn_id'] || $params['invoice_id'])
) {
$contribution = new CRM_Contribute_DAO_Contribution();
if ($params['contribution_id']) {
}
}
else {
- return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.');
+ throw new CRM_Core_Exception('No match found for specified contact in pledge payment data. Row was skipped.');
}
}
else {
$values['contribution_campaign_id'] = $params['contribution_campaign_id'];
break;
- default:
- // Hande name or label for fields with options.
- if (isset($fields[$key]) &&
- // Yay - just for a surprise we are inconsistent on whether we pass the pseudofield (payment_instrument)
- // or the field name (contribution_status_id)
- // @todo - payment_instrument is goneburger - now payment_instrument_id - how
- // can we simplify.
- (!empty($fields[$key]['is_pseudofield_for']) || !empty($fields[$key]['pseudoconstant']))
- ) {
- $realField = $fields[$key]['is_pseudofield_for'] ?? $key;
- $realFieldSpec = $fields[$realField];
- $values[$key] = $this->parsePseudoConstantField($value, $realFieldSpec);
- }
- break;
}
}
* @throws \API_Exception
*/
public function getMappingFieldFromMapperInput(array $fieldMapping, int $mappingID, int $columnNumber): array {
- $isRelationshipField = preg_match('/\d*_a_b|b_a$/', $fieldMapping[0]);
- $fieldName = $isRelationshipField ? $fieldMapping[1] : $fieldMapping[0];
return [
'name' => $fieldMapping[0],
'mapping_id' => $mappingID,
];
}
+ /**
+ * Lookup matching contact.
+ *
+ * This looks up the matching contact from the contact id, external identifier
+ * or email. For the email a straight email search is done - this is equivalent
+ * to what happens on a dedupe rule lookup when the only field is 'email' - but
+ * we can't be sure the rule is 'just email' - and we are not collecting the
+ * fields for any other lookup in the case of soft credits (if we
+ * extend this function to main-contact-lookup we can handle full dedupe
+ * lookups - but note the error messages will need tweaking.
+ *
+ * @param array $params
+ *
+ * @return int
+ * Contact ID
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
+ */
+ private function lookupMatchingContact(array $params): int {
+ $lookupField = !empty($params['contact_id']) ? 'contact_id' : (!empty($params['external_identifier']) ? 'external_identifier' : 'email');
+ if (empty($params['email'])) {
+ $contact = Contact::get(FALSE)->addSelect('id')
+ ->addWhere($lookupField === 'contact_id' ? 'id' : $lookupField, '=', $params[$lookupField])
+ ->execute();
+ if (count($contact) !== 1) {
+ throw new CRM_Core_Exception(ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.",
+ [
+ 1 => $this->getFieldMetadata($lookupField),
+ 2 => $params['contact_id'] ?? $params['external_identifier'],
+ ]));
+ }
+ return $contact->first()['id'];
+ }
+
+ if (!CRM_Utils_Rule::email($params['email'])) {
+ throw new CRM_Core_Exception(ts('Invalid email address %1 provided for Soft Credit. Row was skipped'), [1 => $params['email']]);
+ }
+ $emails = Email::get(FALSE)
+ ->addWhere('contact_id.is_deleted', '=', 0)
+ ->addWhere('contact_id.contact_type', '=', $this->getContactType())
+ ->addWhere('email', '=', $params['email'])
+ ->addSelect('contact_id')->execute();
+ if (count($emails) === 0) {
+ throw new CRM_Core_Exception(ts("Invalid email address(doesn't exist) %1 for Soft Credit. Row was skipped", [1 => $params['email']]));
+ }
+ if (count($emails) > 1) {
+ throw new CRM_Core_Exception(ts('Invalid email address(duplicate) %1 for Soft Credit. Row was skipped', [1 => $params['email']]));
+ }
+ return $emails->first()['contact_id'];
+ }
+
+ /**
+ * @param array $mappedField
+ * Field detail as would be saved in field_mapping table
+ * or as returned from getMappingFieldFromMapperInput
+ *
+ * @return string
+ * @throws \API_Exception
+ */
+ public function getMappedFieldLabel(array $mappedField): string {
+ if (empty($this->importableFieldsMetadata)) {
+ $this->setFieldMetadata();
+ }
+ if ($mappedField['name'] === '') {
+ return '';
+ }
+ $title = [];
+ $title[] = $this->getFieldMetadata($mappedField['name'])['title'];
+ if ($mappedField['soft_credit_match_field']) {
+ $title[] = $this->getFieldMetadata($mappedField['soft_credit_match_field'])['title'];
+ }
+ if ($mappedField['soft_credit_type_id']) {
+ $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', $mappedField['soft_credit_type_id']);
+ }
+
+ return implode(' - ', $title);
+ }
+
+ /**
+ * Get the metadata field for which importable fields does not key the actual field name.
+ *
+ * @return string[]
+ */
+ protected function getOddlyMappedMetadataFields(): array {
+ $uniqueNames = ['contribution_id', 'contribution_contact_id', 'contribution_cancel_date', 'contribution_source', 'contribution_check_number'];
+ $fields = [];
+ foreach ($uniqueNames as $name) {
+ $fields[$this->importableFieldsMetadata[$name]['name']] = $name;
+ }
+ // Include the parent fields as they could be present if required for matching ...in theory.
+ return array_merge($fields, parent::getOddlyMappedMetadataFields());
+ }
+
}