Merge pull request #23753 from eileenmcnaughton/import_validate_int
[civicrm-core.git] / CRM / Contribute / Import / Parser / Contribution.php
index 0daba3335ea5e7d0a6640dfa10a78233e9002a3c..7784c9d9551d28d18369bf357976e1dc16311d03 100644 (file)
@@ -15,6 +15,9 @@
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 
+use Civi\Api4\Contact;
+use Civi\Api4\Email;
+
 /**
  * Class to parse contribution csv files.
  */
@@ -22,11 +25,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
 
   protected $_mapperKeys;
 
-  private $_contactIdIndex;
-
-  protected $_mapperSoftCredit;
-  //protected $_mapperPhoneType;
-
   /**
    * Array of successfully imported contribution id's
    *
@@ -38,15 +36,10 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    * 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;
   }
 
   /**
@@ -55,17 +48,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    */
   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
@@ -129,341 +111,33 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
   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')]];
   }
 
   /**
@@ -486,11 +160,14 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
   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;
@@ -520,197 +197,16 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
     }
   }
 
-  /**
-   * 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++;
-    }
   }
 
   /**
@@ -718,7 +214,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    */
   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,
         [
@@ -726,6 +222,8 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
             'title' => ts('Soft Credit'),
             'softCredit' => TRUE,
             'headerPattern' => '/Soft Credit/i',
+            'options' => FALSE,
+            'type' => CRM_Utils_Type::T_STRING,
           ],
         ]
       );
@@ -733,13 +231,13 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
       // 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,
           ],
         ];
 
@@ -757,311 +255,276 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
   }
 
   /**
-   * 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'])) {
@@ -1079,8 +542,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
         NULL,
         $formatted['total_amount']
       );
-
-      return self::PLEDGE_PAYMENT;
     }
   }
 
@@ -1093,61 +554,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
     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.
    *
@@ -1160,37 +566,16 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    * @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);
-          }
-        }
       }
     }
   }
@@ -1205,19 +590,14 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    * @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
@@ -1225,34 +605,8 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
         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");
           }
@@ -1267,9 +621,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
           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':
@@ -1335,114 +687,32 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
             }
           }
           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']) {
@@ -1462,7 +732,7 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
               }
             }
             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 {
@@ -1541,20 +811,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
           $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;
       }
     }
 
@@ -1593,8 +849,6 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
    * @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,
@@ -1609,4 +863,98 @@ class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser {
     ];
   }
 
+  /**
+   * 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());
+  }
+
 }