diff --git a/CRM/Contact/Import/Parser/Contact.php b/CRM/Contact/Import/Parser/Contact.php index 8eefbf1686dc..234faa7b5be9 100644 --- a/CRM/Contact/Import/Parser/Contact.php +++ b/CRM/Contact/Import/Parser/Contact.php @@ -29,8 +29,6 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser { use CRM_Contact_Import_MetadataTrait; protected $_mapperKeys = []; - protected $_mapperRelated; - protected $_mapperRelatedContactDetails; protected $_relationships; /** @@ -112,19 +110,10 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser { * Class constructor. * * @param array $mapperKeys - * @param array $mapperLocType - * @param array $mapperPhoneType - * @param array $mapperImProvider - * @param array $mapperRelated - * @param array $mapperRelatedContactType - * @param array $mapperRelatedContactDetails */ - public function __construct( - $mapperKeys = [], $mapperLocType = [], $mapperPhoneType = [], $mapperImProvider = [], $mapperRelated = [], $mapperRelatedContactType = [], $mapperRelatedContactDetails = []) { + public function __construct($mapperKeys = []) { parent::__construct(); $this->_mapperKeys = $mapperKeys; - $this->_mapperRelated = &$mapperRelated; - $this->_mapperRelatedContactDetails = &$mapperRelatedContactDetails; } /** @@ -644,15 +633,6 @@ public function import($onDuplicate, &$values) { //format common data, CRM-4062 $this->formatCommonData($field, $formatting, $contactFields); - //do we have enough fields to create related contact. - $allowToCreate = $this->checkRelatedContactFields($key, $formatting); - - if (!$allowToCreate) { - $errorMessage = ts('Related contact required fields are missing.'); - array_unshift($values, $errorMessage); - return CRM_Import_Parser::NO_MATCH; - } - //fixed for CRM-4148 if (!empty($params[$key]['id'])) { $contact = [ @@ -1929,50 +1909,6 @@ public function processMessage(&$values, $statusFieldName, $returnCode) { return $returnCode; } - /** - * @param $relKey - * @param array $params - * - * @return bool - */ - public function checkRelatedContactFields($relKey, $params) { - //avoid blank contact creation. - $allowToCreate = FALSE; - - //build the mapper field array. - static $relatedContactFields = []; - if (!isset($relatedContactFields[$relKey])) { - foreach ($this->_mapperRelated as $key => $name) { - if (!$name) { - continue; - } - - if (!empty($relatedContactFields[$name]) && !is_array($relatedContactFields[$name])) { - $relatedContactFields[$name] = []; - } - $fldName = $this->_mapperRelatedContactDetails[$key] ?? NULL; - if ($fldName == 'url') { - $fldName = 'website'; - } - if ($fldName) { - $relatedContactFields[$name][] = $fldName; - } - } - } - - //validate for passed data. - if (is_array($relatedContactFields[$relKey])) { - foreach ($relatedContactFields[$relKey] as $fld) { - if (!empty($params[$fld])) { - $allowToCreate = TRUE; - break; - } - } - } - - return $allowToCreate; - } - /** * get subtypes given the contact type * @@ -3091,6 +3027,15 @@ public function getMappedRow(array $values): array { public function validateValues(array $values): void { $params = $this->getMappedRow($values); $this->validateRequiredContactFields($params['contact_type'], $params, $this->isUpdateExistingContacts()); + foreach ($params as $key => $value) { + // If the key is a relationship key - eg. 5_a_b or 10_b_a + // then the value is an array that describes an existing contact. + // We need to check the fields are present to identify or create this + // contact. + if (preg_match('/^\d+_[a|b]_[a|b]$/', $key)) { + $this->validateRequiredContactFields($value['contact_type'], $value, TRUE, '(' . $this->getRelatedContactLabel(substr($key, 0, -4), substr($key, -3)) . ')'); + } + } //check for duplicate external Identifier $externalID = $params['external_identifier'] ?? NULL; @@ -3164,12 +3109,39 @@ protected function getRelatedContactType($relationshipTypeID, $relationshipDirec if (!$relationshipTypeID) { return NULL; } - $cacheKey = $relationshipTypeID . $relationshipDirection; + $relationshipField = 'contact_type_' . substr($relationshipDirection, -1); + return $this->getRelationshipType($relationshipTypeID, $relationshipDirection)[$relationshipField]; + } + + /** + * Get the related contact type. + * + * @param int|null $relationshipTypeID + * @param int|string $relationshipDirection + * + * @return null|string + * + * @throws \API_Exception + */ + protected function getRelatedContactLabel($relationshipTypeID, $relationshipDirection): ?string { + $relationshipField = 'label_' . $relationshipDirection; + return $this->getRelationshipType($relationshipTypeID, $relationshipDirection)[$relationshipField]; + } + + /** + * Get the relationship type. + * + * @param int $relationshipTypeID + * + * @return string[] + * @throws \API_Exception + */ + protected function getRelationshipType(int $relationshipTypeID): array { + $cacheKey = 'relationship_type' . $relationshipTypeID; if (!isset(Civi::$statics[__CLASS__][$cacheKey])) { - $relationshipField = 'contact_type_' . substr($relationshipDirection, -1); Civi::$statics[__CLASS__][$cacheKey] = RelationshipType::get(FALSE) ->addWhere('id', '=', $relationshipTypeID) - ->addSelect($relationshipField)->execute()->first()[$relationshipField]; + ->addSelect('*')->execute()->first(); } return Civi::$statics[__CLASS__][$cacheKey]; } diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php index 50109fe150b9..9e9465dfe1a9 100644 --- a/CRM/Import/Parser.php +++ b/CRM/Import/Parser.php @@ -512,11 +512,16 @@ public function setMaxLinesToProcess($max) { * @param string $contactType * @param array $params * @param bool $isPermitExistingMatchFields + * True if the it is enough to have fields which will enable us to find + * an existing contact (eg. external_identifier). + * @param string $prefixString + * String to include in the exception (e.g '(Child of)' if we are validating + * a related contact. * * @return void * @throws \CRM_Core_Exception */ - protected function validateRequiredContactFields(string $contactType, array $params, bool $isPermitExistingMatchFields = TRUE): void { + protected function validateRequiredContactFields(string $contactType, array $params, bool $isPermitExistingMatchFields = TRUE, $prefixString = ''): void { if (!empty($params['id'])) { return; } @@ -536,7 +541,7 @@ protected function validateRequiredContactFields(string $contactType, array $par // specified dedupe rule (or the default Unsupervised if not specified). $requiredFields['email'] = ts('Email Address'); } - $this->validateRequiredFields($requiredFields, $params); + $this->validateRequiredFields($requiredFields, $params, $prefixString); } /** @@ -1127,11 +1132,11 @@ protected function getIdsOfMatchingContacts(array $formatted):array { * ['first_name' => ts('First Name'), 'last_name' => ts('Last Name')] * ] * Means 'email' OR 'first_name AND 'last_name'. + * @param string $prefixString * - * @throws \CRM_Core_Exception - * Exception thrown if field requirements are not met. + * @throws \CRM_Core_Exception Exception thrown if field requirements are not met. */ - protected function validateRequiredFields(array $requiredFields, array $params): void { + protected function validateRequiredFields(array $requiredFields, array $params, $prefixString): void { $missingFields = []; foreach ($requiredFields as $key => $required) { if (!is_array($required)) { @@ -1163,7 +1168,7 @@ protected function validateRequiredFields(array $requiredFields, array $params): $missingFields[$key] = implode(' ' . ts('and') . ' ', $missing); } } - throw new CRM_Core_Exception(ts('Missing required fields:') . ' ' . implode(' ' . ts('OR') . ' ', $missingFields)); + throw new CRM_Core_Exception(($prefixString ? ($prefixString . ' ') : '') . ts('Missing required fields:') . ' ' . implode(' ' . ts('OR') . ' ', $missingFields)); } } diff --git a/tests/phpunit/CRM/Contact/Import/Form/data/individual_invalid_with_related_phone.csv b/tests/phpunit/CRM/Contact/Import/Form/data/individual_invalid_with_related_phone.csv new file mode 100644 index 000000000000..e1bb086ce329 --- /dev/null +++ b/tests/phpunit/CRM/Contact/Import/Form/data/individual_invalid_with_related_phone.csv @@ -0,0 +1,2 @@ +Player First Name,Player Last name,Mother – phone +Susie,Jones,911 diff --git a/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php b/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php index 899de1339c65..5df22b64a3d4 100644 --- a/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php +++ b/tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php @@ -755,6 +755,16 @@ public function validateDataProvider(): array { 'mapper' => [['last_name']], 'expected_error' => 'Missing required fields: First Name OR Email Address', ], + 'individual_related_required_met' => [ + 'csv' => 'individual_valid_with_related_email.csv', + 'mapper' => [['first_name'], ['last_name'], ['1_a_b', 'email']], + 'expected_error' => '', + ], + 'individual_related_required_not_met' => [ + 'csv' => 'individual_invalid_with_related_phone.csv', + 'mapper' => [['first_name'], ['last_name'], ['1_a_b', 'phone', 1, 2]], + 'expected_error' => '(Child of) Missing required fields: First Name and Last Name OR Email Address OR External Identifier', + ], 'individual_bad_email' => [ 'csv' => 'individual_invalid_email.csv', 'mapper' => [['email', 1], ['first_name'], ['last_name']],