From 1149a00c8ac08dfd4aba47c7b34e1e22596038b9 Mon Sep 17 00:00:00 2001
From: Eileen McNaughton <emcnaughton@wikimedia.org>
Date: Thu, 2 Jun 2022 13:37:20 +1200
Subject: [PATCH] Handle error when creating related contact

This addresses the e-notice identified here
https://github.com/civicrm/civicrm-core/pull/23643#discussion_r887406505

I believe the bug report in that thread was a variant of the e-notice. Ie the import()
function was failing to catch an exception & it was bubbling up to run()

While there is no support currently for calling import()
outside of run() - the intent is to replace run() with something
more sane - which WILL be callable from the outside (likely
via api).

The right behaviour for import() is to catch all errors
---
 CRM/Contact/BAO/Contact.php                   |  2 +-
 CRM/Contact/Import/Parser/Contact.php         | 70 ++-----------------
 .../CRM/Contact/Import/Parser/ContactTest.php | 10 ++-
 3 files changed, 15 insertions(+), 67 deletions(-)

diff --git a/CRM/Contact/BAO/Contact.php b/CRM/Contact/BAO/Contact.php
index b808d6ed05..c0928f3e66 100644
--- a/CRM/Contact/BAO/Contact.php
+++ b/CRM/Contact/BAO/Contact.php
@@ -799,7 +799,7 @@ WHERE     civicrm_contact.id = " . CRM_Utils_Type::escape($id, 'Integer');
    *
    * @return CRM_Contact_BAO_Contact
    */
-  public static function &retrieve(&$params, &$defaults, $microformat = FALSE) {
+  public static function &retrieve(&$params, &$defaults = [], $microformat = FALSE) {
     if (array_key_exists('contact_id', $params)) {
       $params['id'] = $params['contact_id'];
     }
diff --git a/CRM/Contact/Import/Parser/Contact.php b/CRM/Contact/Import/Parser/Contact.php
index e9c403f75c..903d7854d1 100644
--- a/CRM/Contact/Import/Parser/Contact.php
+++ b/CRM/Contact/Import/Parser/Contact.php
@@ -400,77 +400,21 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
         //format common data, CRM-4062
         $this->formatCommonData($field, $formatting, $contactFields);
 
-        //fixed for CRM-4148
-        if (!empty($formatting['id'])) {
-          $contact = [
-            'contact_id' => $formatting['id'],
-          ];
-          $defaults = [];
-          $relatedNewContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults);
-        }
-        else {
-          $relatedNewContact = $this->createContact($formatting, $contactFields, $onDuplicate, NULL, FALSE);
-        }
-
-        if (is_object($relatedNewContact) || ($relatedNewContact instanceof CRM_Contact_BAO_Contact)) {
-          $relatedNewContact = clone($relatedNewContact);
-        }
-
-        $matchedIDs = [];
-        // To update/fill contact, get the matching contact Ids if duplicate contact found
-        // otherwise get contact Id from object of related contact
-        if (is_array($relatedNewContact)) {
-          $matchedIDs = $relatedNewContact;
-        }
-        else {
-          $matchedIDs[] = $relatedNewContact->id;
-        }
-        // update/fill related contact after getting matching Contact Ids, CRM-4424
-        if (in_array($onDuplicate, [
-          CRM_Import_Parser::DUPLICATE_UPDATE,
-          CRM_Import_Parser::DUPLICATE_FILL,
-        ])) {
-          //validation of related contact subtype for update mode
-          //CRM-5125
-          $relatedCsType = NULL;
-          if (!empty($formatting['contact_sub_type'])) {
-            $relatedCsType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $matchedIDs[0], 'contact_sub_type');
+        if (empty($formatting['id']) || $this->isUpdateExistingContacts()) {
+          try {
+            $relatedNewContact = $this->createContact($formatting, $contactFields, $onDuplicate, $formatting['id']);
           }
-
-          if (!empty($relatedCsType) && (!CRM_Contact_BAO_ContactType::isAllowEdit($matchedIDs[0], $relatedCsType) && $relatedCsType != CRM_Utils_Array::value('contact_sub_type', $formatting))) {
-            $this->setImportStatus((int) $values[count($values) - 1], 'invalid_no_match', 'Mismatched or Invalid contact subtype found for this related contact.');
+          catch (CiviCRM_API3_Exception $e) {
+            $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage());
             return FALSE;
           }
-          else {
-            $this->createContact($formatting, $contactFields, $onDuplicate, $matchedIDs[0]);
-          }
-        }
-        static $relativeContact = [];
-        if (is_array($relatedNewContact)) {
-          if (count($matchedIDs) >= 1) {
-            $relContactId = $matchedIDs[0];
-            //add relative contact to count during update & fill mode.
-            //logic to make count distinct by contact id.
-            if ($this->_newRelatedContacts || !empty($relativeContact)) {
-              $reContact = array_keys($relativeContact, $relContactId);
-
-              if (empty($reContact)) {
-                $this->_newRelatedContacts[] = $relativeContact[] = $relContactId;
-              }
-            }
-            else {
-              $this->_newRelatedContacts[] = $relativeContact[] = $relContactId;
-            }
-          }
-        }
-        else {
           $relContactId = $relatedNewContact->id;
-          $this->_newRelatedContacts[] = $relativeContact[] = $relContactId;
+          $this->_newRelatedContacts[$relContactId] = $relContactId;
         }
 
         if (1) {
           //fix for CRM-1993.Checks for duplicate related contacts
-          if (count($matchedIDs) >= 1) {
+          if (1) {
             //if more than one duplicate contact
             //found, create relationship with first contact
             // now create the relationship record
diff --git a/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php b/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php
index 32ae637ce3..17eebe4cad 100644
--- a/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php
+++ b/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php
@@ -2111,16 +2111,20 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
   /**
    * Test that import parser will not throw error if Related Contact is not found via passed in External ID.
    *
+   * If the organization is present it will create it - otherwise fail without error.
+   *
+   * @dataProvider getBooleanDataProvider
+   *
    * @throws \API_Exception
    * @throws \CRM_Core_Exception
    * @throws \CiviCRM_API3_Exception
    */
-  public function testImportParserWithExternalIdForRelationship(): void {
+  public function testImportParserWithExternalIdForRelationship(bool $isOrganizationProvided): void {
     $contactImportValues = [
       'first_name' => 'Alok',
       'last_name' => 'Patel',
       'Employee of' => 'related external identifier',
-      'organization_name' => 'Big shop',
+      'organization_name' => $isOrganizationProvided ? 'Big shop' : '',
     ];
 
     $mapper = [
@@ -2140,7 +2144,7 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
     $parser->init();
 
     $parser->import(CRM_Import_Parser::DUPLICATE_UPDATE, $values);
-    $this->callAPISuccessGetCount('Contact', ['organization_name' => 'Big shop'], 2);
+    $this->callAPISuccessGetCount('Contact', ['organization_name' => 'Big shop'], $isOrganizationProvided ? 2 : 0);
   }
 
 }
-- 
2.25.1