From 638a0140965d3044bb5870e5ba50de15f9eb7a20 Mon Sep 17 00:00:00 2001
From: Eileen McNaughton <emcnaughton@wikimedia.org>
Date: Fri, 3 Jun 2022 15:23:29 +1200
Subject: [PATCH 1/4] Add import labels fixes that I figured out doing
 memberhsip

Importantly do_not_import should be added
---
 CRM/Contribute/Import/Form/MapField.php      | 2 +-
 CRM/Upgrade/Incremental/php/FiveFiftyOne.php | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/CRM/Contribute/Import/Form/MapField.php b/CRM/Contribute/Import/Form/MapField.php
index 78e6a869d8b1..8c24947a2bd4 100644
--- a/CRM/Contribute/Import/Form/MapField.php
+++ b/CRM/Contribute/Import/Form/MapField.php
@@ -433,7 +433,7 @@ public function postProcess() {
       if (isset($mapperKeys[$i][0]) && $mapperKeys[$i][0] == 'soft_credit') {
         $mapperSoftCredit[$i] = $mapperKeys[$i][1];
         if (strpos($mapperSoftCredit[$i], '_') !== FALSE) {
-          list($first, $second) = explode('_', $mapperSoftCredit[$i]);
+          [$first, $second] = explode('_', $mapperSoftCredit[$i]);
           $softCreditFields[$i] = ucwords($first . " " . $second);
         }
         else {
diff --git a/CRM/Upgrade/Incremental/php/FiveFiftyOne.php b/CRM/Upgrade/Incremental/php/FiveFiftyOne.php
index fe10b78e6330..3de256e0ff4b 100644
--- a/CRM/Upgrade/Incremental/php/FiveFiftyOne.php
+++ b/CRM/Upgrade/Incremental/php/FiveFiftyOne.php
@@ -71,6 +71,7 @@ public static function fillQueueColumns($ctx): bool {
    * @throws \API_Exception
    */
   public static function convertMappingFieldLabelsToNames(): bool {
+    // Contribution fields....
     $mappings = MappingField::get(FALSE)
       ->setSelect(['id', 'name'])
       ->addWhere('mapping_id.mapping_type_id:name', '=', 'Import Contribution')
@@ -79,12 +80,16 @@ public static function convertMappingFieldLabelsToNames(): bool {
     $fieldMap = [];
     foreach ($fields as $fieldName => $field) {
       $fieldMap[$field['title']] = $fieldName;
+      if (!empty($field['html']['label'])) {
+        $fieldMap[$field['html']['label']] = $fieldName;
+      }
     }
     $fieldMap[ts('Soft Credit')] = 'soft_credit';
     $fieldMap[ts('Pledge Payment')] = 'pledge_payment';
     $fieldMap[ts(ts('Pledge ID'))] = 'pledge_id';
     $fieldMap[ts(ts('Financial Type'))] = 'financial_type_id';
     $fieldMap[ts(ts('Payment Method'))] = 'payment_instrument_id';
+    $fieldMap[ts('- do not import -')] = 'do_not_import';
 
     foreach ($mappings as $mapping) {
       if (!empty($fieldMap[$mapping['name']])) {

From 82cf0710d4b7a0807d8b53dfe463ef0c07cdce14 Mon Sep 17 00:00:00 2001
From: Eileen McNaughton <emcnaughton@wikimedia.org>
Date: Fri, 3 Jun 2022 15:24:14 +1200
Subject: [PATCH 2/4] Fix fatal when going back from MapField due to no
 dataSource

---
 CRM/Import/Form/DataSource.php | 5 ++++-
 CRM/Import/Forms.php           | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/CRM/Import/Form/DataSource.php b/CRM/Import/Form/DataSource.php
index 19fea5947c19..a5e53ad43fd8 100644
--- a/CRM/Import/Form/DataSource.php
+++ b/CRM/Import/Form/DataSource.php
@@ -63,7 +63,10 @@ protected function getTranslatedEntities(): string {
    */
   public function buildQuickForm() {
     $config = CRM_Core_Config::singleton();
-
+    // When we switch to using the DataSource.tpl used by Contact we can remove this in
+    // favour of the one used by Contact - I was trying to consolidate
+    // first & got stuck on https://github.com/civicrm/civicrm-core/pull/23458
+    $this->add('hidden', 'hidden_dataSource', 'CRM_Import_DataSource_CSV');
     $uploadFileSize = CRM_Utils_Number::formatUnitSize($config->maxFileSize . 'm', TRUE);
 
     //Fetch uploadFileSize from php_ini when $config->maxFileSize is set to "no limit".
diff --git a/CRM/Import/Forms.php b/CRM/Import/Forms.php
index c3f364600213..e87bc5acb747 100644
--- a/CRM/Import/Forms.php
+++ b/CRM/Import/Forms.php
@@ -137,7 +137,10 @@ public function getSubmittedValue(string $fieldName) {
     if ($fieldName === 'dataSource') {
       // Hard-coded handling for DataSource as it affects the contents of
       // getSubmittableFields and can cause a loop.
-      return $this->controller->exportValue('DataSource', 'dataSource');
+      // Note that the non-contact imports are not currently sharing the DataSource.tpl
+      // that adds the CSV/SQL options & hence fall back on this hidden field.
+      // - todo - switch to the same DataSource.tpl for all.
+      return $this->controller->exportValue('DataSource', 'dataSource') ?? $this->controller->exportValue('DataSource', 'hidden_dataSource');
     }
     $mappedValues = $this->getSubmittableFields();
     if (array_key_exists($fieldName, $mappedValues)) {

From 8daa4f362f8b1d0a3a79f23827b7740786d1822e Mon Sep 17 00:00:00 2001
From: Eileen McNaughton <emcnaughton@wikimedia.org>
Date: Fri, 3 Jun 2022 17:12:05 +1200
Subject: [PATCH 3/4] Cleanup templates & form variables, following contact
 pattern

---
 CRM/Contribute/Import/Form/MapField.php       |  37 ++----
 CRM/Contribute/Import/Form/Preview.php        |  32 +----
 CRM/Contribute/Import/Parser/Contribution.php | 113 +++++++-----------
 .../CRM/Contribute/Import/Form/MapTable.tpl   |  93 +-------------
 .../CRM/Contribute/Import/Form/Preview.tpl    |   2 +-
 5 files changed, 61 insertions(+), 216 deletions(-)

diff --git a/CRM/Contribute/Import/Form/MapField.php b/CRM/Contribute/Import/Form/MapField.php
index 8c24947a2bd4..b45767d8b141 100644
--- a/CRM/Contribute/Import/Form/MapField.php
+++ b/CRM/Contribute/Import/Form/MapField.php
@@ -72,27 +72,15 @@ protected static function checkRequiredFields($self, string $contactORContributi
    * Set variables up before form is built.
    */
   public function preProcess() {
+    parent::preProcess();
     $this->_mapperFields = $this->getAvailableFields();
     asort($this->_mapperFields);
 
     $this->_columnCount = $this->get('columnCount');
-    $this->assign('columnCount', $this->_columnCount);
-    $this->_dataValues = $this->get('dataValues');
-    $this->assign('dataValues', $this->_dataValues);
+    $skipColumnHeader = $this->getSubmittedValue('skipColumnHeader');
+    $this->_onDuplicate = $this->getSubmittedValue('onDuplicate');
+    $this->assign('skipColumnHeader', $skipColumnHeader);
 
-    $skipColumnHeader = $this->controller->exportValue('DataSource', 'skipColumnHeader');
-    $this->_onDuplicate = $this->get('onDuplicate', $onDuplicate ?? "");
-
-    if ($skipColumnHeader) {
-      $this->assign('skipColumnHeader', $skipColumnHeader);
-      $this->assign('rowDisplayCount', 3);
-      // If we had a column header to skip, stash it for later
-
-      $this->_columnHeaders = $this->_dataValues[0];
-    }
-    else {
-      $this->assign('rowDisplayCount', 2);
-    }
     $highlightedFields = ['financial_type_id', 'total_amount'];
     //CRM-2219 removing other required fields since for updation only
     //invoice id or trxn id or contribution id is required.
@@ -157,10 +145,11 @@ public function buildQuickForm() {
 
     $defaults = [];
     $mapperKeys = array_keys($this->_mapperFields);
-    $hasHeaders = !empty($this->_columnHeaders);
-    $headerPatterns = $this->get('headerPatterns');
-    $dataPatterns = $this->get('dataPatterns');
-    $mapperKeysValues = $this->controller->exportValue($this->_name, 'mapper');
+    $hasHeaders = $this->getSubmittedValue('skipColumnHeader');
+    $headerPatterns = $this->getHeaderPatterns();
+    $dataPatterns = $this->getDataPatterns();
+    $mapperKeysValues = $this->getSubmittedValue('mapper');
+    $columnHeaders = $this->getColumnHeaders();
 
     /* Initialize all field usages to false */
     foreach ($mapperKeys as $key) {
@@ -188,7 +177,7 @@ public function buildQuickForm() {
     //used to warn for mismatch column count or mismatch mapping
     $warning = 0;
 
-    for ($i = 0; $i < $this->_columnCount; $i++) {
+    foreach ($columnHeaders as $i => $columnHeader) {
       $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', [1 => $i]), NULL);
       $jsSet = FALSE;
       if ($this->get('savedMapping')) {
@@ -228,7 +217,7 @@ public function buildQuickForm() {
           $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n";
 
           if ($hasHeaders) {
-            $defaults["mapper[$i]"] = [$this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns)];
+            $defaults["mapper[$i]"] = [$this->defaultFromHeader($columnHeader, $headerPatterns)];
           }
           else {
             $defaults["mapper[$i]"] = [$this->defaultFromData($dataPatterns, $i)];
@@ -240,7 +229,7 @@ public function buildQuickForm() {
         $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n";
         if ($hasHeaders) {
           // do array search first to see if has mapped key
-          $columnKey = array_search($this->_columnHeaders[$i], $this->_mapperFields);
+          $columnKey = array_search($columnHeader, $this->_mapperFields);
           if (isset($this->_fieldUsed[$columnKey])) {
             $defaults["mapper[$i]"] = $columnKey;
             $this->_fieldUsed[$key] = TRUE;
@@ -248,7 +237,7 @@ public function buildQuickForm() {
           else {
             // Infer the default from the column names if we have them
             $defaults["mapper[$i]"] = [
-              $this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns),
+              $this->defaultFromHeader($columnHeader, $headerPatterns),
               0,
             ];
           }
diff --git a/CRM/Contribute/Import/Form/Preview.php b/CRM/Contribute/Import/Form/Preview.php
index 46d77feedb0e..bce405c8e8a7 100644
--- a/CRM/Contribute/Import/Form/Preview.php
+++ b/CRM/Contribute/Import/Form/Preview.php
@@ -25,38 +25,16 @@ class CRM_Contribute_Import_Form_Preview extends CRM_Import_Form_Preview {
    */
   public function preProcess() {
     parent::preProcess();
-    //get the data from the session
-    $dataValues = $this->get('dataValues');
-    $invalidRowCount = $this->get('invalidRowCount');
-
-    //get the mapping name displayed if the mappingId is set
-    $mappingId = $this->get('loadMappingId');
-    if ($mappingId) {
-      $mapDAO = new CRM_Core_DAO_Mapping();
-      $mapDAO->id = $mappingId;
-      $mapDAO->find(TRUE);
-    }
-    $this->assign('savedMappingName', $mappingId ? $mapDAO->name : NULL);
+    $invalidRowCount = $this->getRowCount(CRM_Import_Parser::VALID);
 
+    $downloadURL = '';
     if ($invalidRowCount) {
       $urlParams = 'type=' . CRM_Import_Parser::ERROR . '&parser=CRM_Contribute_Import_Parser_Contribution';
-      $this->set('downloadErrorRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams));
+      $downloadURL = CRM_Utils_System::url('civicrm/export', $urlParams);
     }
 
-    $properties = [
-      'dataValues',
-      'columnCount',
-      'totalRowCount',
-      'validRowCount',
-      'invalidRowCount',
-      'downloadErrorRecordsUrl',
-    ];
     $this->setStatusUrl();
-    $this->assign('mapper', $this->getMappedFieldLabels());
-
-    foreach ($properties as $property) {
-      $this->assign($property, $this->get($property));
-    }
+    $this->assign('downloadErrorRecordsUrl', $downloadURL);
   }
 
   /**
@@ -105,7 +83,7 @@ public function postProcess() {
       $mapperFields,
       $this->getSubmittedValue('skipColumnHeader'),
       CRM_Import_Parser::MODE_IMPORT,
-      $this->get('contactType'),
+      $this->getSubmittedValue('contactType'),
       $onDuplicate,
       $this->get('statusID'),
       $this->get('totalRowCount')
diff --git a/CRM/Contribute/Import/Parser/Contribution.php b/CRM/Contribute/Import/Parser/Contribution.php
index c818b09475fd..239180da6546 100644
--- a/CRM/Contribute/Import/Parser/Contribution.php
+++ b/CRM/Contribute/Import/Parser/Contribution.php
@@ -48,17 +48,6 @@ public function __construct($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
@@ -137,7 +126,6 @@ public function __construct($mapperKeys = []) {
    * @param int $contactType
    * @param int $onDuplicate
    * @param int $statusID
-   * @param int $totalRowCount
    *
    * @return mixed
    * @throws Exception
@@ -150,13 +138,8 @@ public function run(
     $mode = self::MODE_PREVIEW,
     $contactType = self::CONTACT_INDIVIDUAL,
     $onDuplicate = self::DUPLICATE_SKIP,
-    $statusID = NULL,
-    $totalRowCount = NULL
+    $statusID = NULL
   ) {
-    if (!is_array($fileName)) {
-      throw new CRM_Core_Exception('Unable to determine import file');
-    }
-    $fileName = $fileName['name'];
     // Since $this->_contactType is still being called directly do a get call
     // here to make sure it is instantiated.
     $this->getContactType();
@@ -165,13 +148,6 @@ public function run(
 
     $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;
@@ -185,8 +161,6 @@ public function run(
       $startTimestamp = $currTimestamp = $prevTimestamp = time();
     }
 
-    $this->_fileSize = number_format(filesize($fileName) / 1024.0, 2);
-
     if ($mode == self::MODE_MAPFIELD) {
       $this->_rows = [];
     }
@@ -194,32 +168,13 @@ public function run(
       $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");
-      }
+    $dataSource = $this->getDataSourceObject();
+    $totalRowCount = $dataSource->getRowCount(['new']);
+    $dataSource->setStatuses(['new']);
 
-      if (CRM_Utils_System::isNull($values)) {
-        continue;
-      }
+    while ($row = $dataSource->getRow()) {
+      $values = array_values($row);
+      $this->_lineCount++;
 
       $this->_totalCount++;
 
@@ -309,15 +264,8 @@ public function run(
           $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;
 
@@ -470,18 +418,10 @@ public function addField($name, $title, $type = CRM_Utils_Type::T_INT, $headerPa
    * @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);
@@ -692,6 +632,7 @@ protected function setFieldMetadata() {
    *   CRM_Import_Parser::VALID or CRM_Import_Parser::ERROR
    */
   public function summary(&$values) {
+    $rowNumber = (int) ($values[array_key_last($values)]);
     $params = $this->getMappedRow($values);
 
     //for date-Formats
@@ -707,6 +648,7 @@ public function summary(&$values) {
       $tempMsg = "Invalid value for field(s) : $errorMessage";
       array_unshift($values, $tempMsg);
       $errorMessage = NULL;
+      $this->setImportStatus($rowNumber, 'ERROR', $tempMsg);
       return CRM_Import_Parser::ERROR;
     }
 
@@ -732,6 +674,7 @@ public function summary(&$values) {
    *   - CRM_Import_Parser::PLEDGE_PAYMENT (successful creation)
    */
   public function import($onDuplicate, &$values) {
+    $rowNumber = (int) ($values[array_key_last($values)]);
     // first make sure this is a valid line
     $response = $this->summary($values);
     if ($response != CRM_Import_Parser::VALID) {
@@ -779,6 +722,7 @@ public function import($onDuplicate, &$values) {
     catch (CRM_Core_Exception $e) {
       array_unshift($values, $e->getMessage());
       $errorMapping = ['soft_credit' => self::SOFT_CREDIT_ERROR, 'pledge_payment' => self::PLEDGE_PAYMENT_ERROR];
+      $this->setImportStatus($rowNumber, $errorMapping[$e->getErrorCode()] ?? CRM_Import_Parser::ERROR, $e->getMessage());
       return $errorMapping[$e->getErrorCode()] ?? CRM_Import_Parser::ERROR;
     }
 
@@ -790,6 +734,7 @@ public function import($onDuplicate, &$values) {
       if (CRM_Utils_Array::value('error_data', $formatError) == 'pledge_payment') {
         return self::PLEDGE_PAYMENT_ERROR;
       }
+      $this->setImportStatus($rowNumber, 'ERROR', '');
       return CRM_Import_Parser::ERROR;
     }
 
@@ -873,6 +818,7 @@ public function import($onDuplicate, &$values) {
         }
         $errorMsg = implode(' AND ', $errorMsg);
         array_unshift($values, 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.');
+        $this->setImportStatus($rowNumber, 'ERROR', 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.');
         return CRM_Import_Parser::ERROR;
       }
     }
@@ -885,6 +831,7 @@ public function import($onDuplicate, &$values) {
         $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');
+          $this->setImportStatus($rowNumber, 'ERROR', 'Multiple matching contact records detected for this row. The contribution was not imported');
           return CRM_Import_Parser::ERROR;
         }
         $cid = $matchedIDs[0];
@@ -895,11 +842,13 @@ public function import($onDuplicate, &$values) {
           if (is_array($newContribution['error_message'])) {
             array_unshift($values, $newContribution['error_message']['message']);
             if ($newContribution['error_message']['params'][0]) {
+              $this->setImportStatus($rowNumber, 'DUPLICATE', $newContribution['error_message']['message']);
               return CRM_Import_Parser::DUPLICATE;
             }
           }
           else {
             array_unshift($values, $newContribution['error_message']);
+            $this->setImportStatus($rowNumber, 'ERROR', $newContribution['error_message']);
             return CRM_Import_Parser::ERROR;
           }
         }
@@ -943,8 +892,9 @@ public function import($onDuplicate, &$values) {
           $disp = $params['external_identifier'];
         }
       }
-
-      array_unshift($values, 'No matching Contact found for (' . $disp . ')');
+      $errorMessage = 'No matching Contact found for (' . $disp . ')';
+      $this->setImportStatus($rowNumber, 'ERROR', $errorMessage);
+      array_unshift($values, $errorMessage);
       return CRM_Import_Parser::ERROR;
     }
 
@@ -953,7 +903,9 @@ public function import($onDuplicate, &$values) {
       $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']);
+        $errorMessage = 'Mismatch of External ID:' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id'];
+        array_unshift($values, $errorMessage);
+        $this->setImportStatus($rowNumber, 'ERROR', $errorMessage);
         return CRM_Import_Parser::ERROR;
       }
     }
@@ -962,11 +914,13 @@ public function import($onDuplicate, &$values) {
       if (is_array($newContribution['error_message'])) {
         array_unshift($values, $newContribution['error_message']['message']);
         if ($newContribution['error_message']['params'][0]) {
+          $this->setImportStatus($rowNumber, 'DUPLICATE', '');
           return CRM_Import_Parser::DUPLICATE;
         }
       }
       else {
         array_unshift($values, $newContribution['error_message']);
+        $this->setImportStatus($rowNumber, 'ERROR', $newContribution['error_message']);
         return CRM_Import_Parser::ERROR;
       }
     }
@@ -1509,7 +1463,7 @@ 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, '=', $params[$lookupField])
+        ->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.",
@@ -1562,4 +1516,19 @@ public function getMappedFieldLabel(array $mappedField): string {
     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());
+  }
+
 }
diff --git a/templates/CRM/Contribute/Import/Form/MapTable.tpl b/templates/CRM/Contribute/Import/Form/MapTable.tpl
index 458bf3b4fe76..3b9bab32282d 100644
--- a/templates/CRM/Contribute/Import/Form/MapTable.tpl
+++ b/templates/CRM/Contribute/Import/Form/MapTable.tpl
@@ -8,95 +8,4 @@
  +--------------------------------------------------------------------+
 *}
 {* Contribution Import Wizard - Data Mapping table used by MapFields.tpl and Preview.tpl *}
-
- <div id="map-field">
-    {strip}
-    <table>
-      {if $savedMappingName}
-        <tr class="columnheader-dark"><th colspan="4">{ts 1=$savedMappingName}Saved Field Mapping: %1{/ts}</th></tr>
-      {/if}
-        <tr class="columnheader">
-          {section name=rows loop=$rowDisplayCount}
-            {if $skipColumnHeader }
-              {if $smarty.section.rows.iteration == 1}
-                <th>{ts}Column Headers{/ts}</th>
-              {else}
-                <th>{ts 1=$smarty.section.rows.iteration}Import Data (row %1){/ts}</th>
-              {/if}
-            {else}
-              <th>{ts 1=$smarty.section.rows.iteration}Import Data (row %1){/ts}</th>
-            {/if}
-          {/section}
-
-          <th>{ts}Matching CiviCRM Field{/ts}</th>
-        </tr>
-
-        {*Loop on columns parsed from the import data rows*}
-        {section name=cols loop=$columnCount}
-            {assign var="i" value=$smarty.section.cols.index}
-            <tr style="border-bottom: 1px solid #92B6EC;">
-
-                {section name=rows loop=$rowDisplayCount}
-                    {assign var="j" value=$smarty.section.rows.index}
-                    <td class="{if $skipColumnHeader AND $smarty.section.rows.iteration == 1}even-row labels{else}odd-row{/if}">{$dataValues[$j][$i]|escape}</td>
-                {/section}
-
-                {* Display mapper <select> field for 'Map Fields', and mapper value for 'Preview' *}
-                <td class="form-item even-row{if $wizard.currentStepName == 'Preview'} labels{/if}">
-                    {if $wizard.currentStepName == 'Preview'}
-          {$mapper[$i]}
-                    {else}
-                        {$form.mapper[$i].html|smarty:nodefaults}
-                    {/if}
-                </td>
-
-            </tr>
-        {/section}
-
-    </table>
-  {/strip}
-
-    {if $wizard.currentStepName != 'Preview'}
-    <div>
-
-      {if $savedMappingName}
-          <span>{$form.updateMapping.html} &nbsp;&nbsp; {$form.updateMapping.label}</span>
-      {/if}
-      <span>{$form.saveMapping.html} &nbsp;&nbsp; {$form.saveMapping.label}</span>
-      <div id="saveDetails" class="form-item">
-            <table class="form-layout-compressed">
-           <tr>
-          <td class="label">{$form.saveMappingName.label}</td>
-          <td class="html-adjust">{$form.saveMappingName.html}</td>
-       </tr>
-           <tr>
-          <td class="label">{$form.saveMappingDesc.label}</td>
-          <td class="html-adjust">{$form.saveMappingDesc.html}</td>
-       </tr>
-            </table>
-      </div>
-      <script type="text/javascript">
-             {if $mappingDetailsError }
-                cj('#saveDetails').show();
-             {else}
-              cj('#saveDetails').hide();
-             {/if}
-
-           {literal}
-            function showSaveDetails(chkbox) {
-             if (chkbox.checked) {
-              document.getElementById("saveDetails").style.display = "block";
-              document.getElementById("saveMappingName").disabled = false;
-              document.getElementById("saveMappingDesc").disabled = false;
-             } else {
-              document.getElementById("saveDetails").style.display = "none";
-              document.getElementById("saveMappingName").disabled = true;
-              document.getElementById("saveMappingDesc").disabled = true;
-             }
-             }
-             {/literal}
-       {include file="CRM/common/highLightImport.tpl"}
-      </script>
-    </div>
-    {/if}
- </div>
+{include file="CRM/Import/Form/MapTableCommon.tpl" mapper=$form.mapper}
diff --git a/templates/CRM/Contribute/Import/Form/Preview.tpl b/templates/CRM/Contribute/Import/Form/Preview.tpl
index bf4557bcf0c0..61f1d14e11f9 100644
--- a/templates/CRM/Contribute/Import/Form/Preview.tpl
+++ b/templates/CRM/Contribute/Import/Form/Preview.tpl
@@ -54,6 +54,6 @@
 
 
  {* Table for mapping preview *}
- {include file="CRM/Contribute/Import/Form/MapTable.tpl"}
+{include file="CRM/Import/Form/MapTableCommon.tpl"}
   <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
  </div>

From 1c82489babc4981ba9b99f48abe0a1e0d3d496a7 Mon Sep 17 00:00:00 2001
From: Eileen McNaughton <emcnaughton@wikimedia.org>
Date: Fri, 3 Jun 2022 18:34:50 +1200
Subject: [PATCH 4/4] Use getTransformedValue more & really old code less

---
 CRM/Contribute/Import/Parser/Contribution.php | 67 +------------------
 CRM/Import/Parser.php                         |  2 +-
 2 files changed, 4 insertions(+), 65 deletions(-)

diff --git a/CRM/Contribute/Import/Parser/Contribution.php b/CRM/Contribute/Import/Parser/Contribution.php
index 239180da6546..96aa3cc236e1 100644
--- a/CRM/Contribute/Import/Parser/Contribution.php
+++ b/CRM/Contribute/Import/Parser/Contribution.php
@@ -380,7 +380,7 @@ public function getMappedRow(array $values): array {
         $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;
@@ -634,16 +634,9 @@ protected function setFieldMetadata() {
   public function summary(&$values) {
     $rowNumber = (int) ($values[array_key_last($values)]);
     $params = $this->getMappedRow($values);
-
-    //for date-Formats
-    $errorMessage = implode('; ', $this->formatDateFields($params));
-    //date-Format part ends
-
+    $errorMessage = implode(';', $this->getInvalidValues($params));
     $params['contact_type'] = 'Contribution';
 
-    //checking error in custom data
-    $this->isErrorInCustomData($params, $errorMessage);
-
     if ($errorMessage) {
       $tempMsg = "Invalid value for field(s) : $errorMessage";
       array_unshift($values, $tempMsg);
@@ -682,7 +675,7 @@ public function import($onDuplicate, &$values) {
     }
 
     $params = $this->getMappedRow($values);
-    $formatted = ['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE, 'contribution_id' => $params['id'] ?? NULL];
+    $formatted = array_merge(['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE, 'contribution_id' => $params['id'] ?? NULL], $params);
     //CRM-10994
     if (isset($params['total_amount']) && $params['total_amount'] == 0) {
       $params['total_amount'] = '0.00';
@@ -1095,11 +1088,6 @@ private function deprecatedFormatParams($params, &$values, $create = FALSE, $onD
     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
@@ -1107,32 +1095,6 @@ private function deprecatedFormatParams($params, &$values, $create = FALSE, $onD
         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 'contact_id':
           if (!CRM_Utils_Rule::integer($value)) {
@@ -1221,15 +1183,6 @@ private function deprecatedFormatParams($params, &$values, $create = FALSE, $onD
           }
           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':
@@ -1374,20 +1327,6 @@ private function deprecatedFormatParams($params, &$values, $create = FALSE, $onD
           $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;
       }
     }
 
diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php
index 8a12340f48fb..93c5260ff197 100644
--- a/CRM/Import/Parser.php
+++ b/CRM/Import/Parser.php
@@ -1537,7 +1537,7 @@ public function validate(): void {
    * @return array
    * @throws \API_Exception
    */
-  protected function getInvalidValues($value, string $key, string $prefixString = ''): array {
+  protected function getInvalidValues($value, string $key = '', string $prefixString = ''): array {
     $errors = [];
     if ($value === 'invalid_import_value') {
       $errors[] = $prefixString . $this->getFieldMetadata($key)['title'];