Skip to content

Commit

Permalink
[Import] Check subtype validity in validate rather than wait for 'imp…
Browse files Browse the repository at this point in the history
…ort', test
  • Loading branch information
eileenmcnaughton committed May 19, 2022
1 parent d753d3d commit 5f6ad41
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 71 deletions.
62 changes: 12 additions & 50 deletions CRM/Contact/Import/Parser/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,34 +299,21 @@ public function import($onDuplicate, &$values) {
}

$params = $this->getMappedRow($values);
$formatted = [
'contact_type' => $this->getContactType(),
];
$formatted = array_filter(array_intersect_key($params, ['contact_type' => 1, 'contact_sub_type' => 1]));

$contactFields = CRM_Contact_DAO_Contact::import();

$params['contact_sub_type'] = $this->getContactSubType() ?: ($params['contact_sub_type'] ?? NULL);

if ($params['contact_sub_type']) {
if (CRM_Contact_BAO_ContactType::isExtendsContactType($params['contact_sub_type'], $this->getContactType(), FALSE, 'label')) {
// I think this bit is switching a passed in label to
// a name.
$subTypes = CRM_Contact_BAO_ContactType::subTypePairs($this->getContactType(), FALSE, NULL);
$params['contact_sub_type'] = array_search($params['contact_sub_type'], $subTypes);
}
}

try {
if ($params['contact_sub_type'] && !CRM_Contact_BAO_ContactType::isExtendsContactType($params['contact_sub_type'], $this->getContactType())) {
throw new CRM_Core_Exception('Mismatched or Invalid Contact Subtype.', CRM_Import_Parser::NO_MATCH);
}
$params['id'] = $formatted['id'] = $this->lookupContactID($params, ($this->isSkipDuplicates() || $this->isIgnoreDuplicates()));
}
catch (CRM_Core_Exception $e) {
$statuses = [CRM_Import_Parser::DUPLICATE => 'DUPLICATE', CRM_Import_Parser::ERROR => 'ERROR', CRM_Import_Parser::NO_MATCH => 'invalid_no_match'];
$this->setImportStatus((int) $values[count($values) - 1], $statuses[$e->getErrorCode()], $e->getMessage());
return FALSE;
}

// Get contact id to format common data in update/fill mode,
// prioritising a dedupe rule check over an external_identifier check, but falling back on ext id.

Expand Down Expand Up @@ -820,21 +807,7 @@ private static function legacyCreateMultiple($params, $ids = []) {
* Contact DAO fields.
*/
private function formatCommonData($params, &$formatted, $contactFields) {
$csType = [
CRM_Utils_Array::value('contact_type', $formatted),
];

//CRM-5125
//add custom fields for contact sub type
if (!empty($this->_contactSubType)) {
$csType = $this->_contactSubType;
}

if ($relCsType = CRM_Utils_Array::value('contact_sub_type', $formatted)) {
$csType = $relCsType;
}

$customFields = CRM_Core_BAO_CustomField::getFields($formatted['contact_type'], FALSE, FALSE, $csType);
$customFields = CRM_Core_BAO_CustomField::getFields($formatted['contact_type'], FALSE, FALSE, $formatted['contact_sub_type'] ?? NULL);

$addressCustomFields = CRM_Core_BAO_CustomField::getFields('Address');
$customFields = $customFields + $addressCustomFields;
Expand Down Expand Up @@ -1261,6 +1234,10 @@ public static function isErrorInCustomData($params, &$errorMessage, $csType = NU
*/
public function isErrorInCoreData($params, &$errorMessage) {
$errors = [];
if (!empty($params['contact_sub_type']) && !CRM_Contact_BAO_ContactType::isExtendsContactType($params['contact_sub_type'], $params['contact_type'])) {
$errors[] = ts('Mismatched or Invalid Contact Subtype.');
}

foreach ($params as $key => $value) {
if ($value === 'invalid_import_value') {
$errors[] = $this->getFieldMetadata($key)['title'];
Expand Down Expand Up @@ -2039,16 +2016,6 @@ public function deprecated_contact_check_params(
$dupeCheck = TRUE,
$dedupeRuleGroupID = NULL) {

if (isset($params['id']) && is_numeric($params['id'])) {
// @todo - ensure this is tested & remove - expectation is api call further
// down validates it.
if ($csType = CRM_Utils_Array::value('contact_sub_type', $params)) {
if (!(CRM_Contact_BAO_ContactType::isExtendsContactType($csType, $params['contact_type']))) {
throw new CRM_Core_Exception("Invalid or Mismatched Contact Subtype: " . implode(', ', (array) $csType));
}
}
}

if ($dupeCheck) {
// @todo switch to using api version
// $dupes = civicrm_api3('Contact', 'duplicatecheck', (array('match' => $params, 'dedupe_rule_id' => $dedupeRuleGroupID)));
Expand Down Expand Up @@ -2724,7 +2691,7 @@ public function getMappedFieldLabel(array $mappedField): string {
if ($mappedField['relationship_type_id']) {
$title[] = $this->getRelationshipLabel($mappedField['relationship_type_id'], $mappedField['relationship_direction']);
}
$title[] = $this->getImportableFieldsMetadata()[$mappedField['name']]['title'];
$title[] = $this->getFieldMetadata($mappedField['name'])['title'];
if ($mappedField['location_type_id']) {
$title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Address', 'location_type_id', $mappedField['location_type_id']);
}
Expand Down Expand Up @@ -2783,6 +2750,9 @@ protected function getRelationshipLabel(int $id, string $direction): string {
public function getMappedRow(array $values): array {
$params = $this->getParams($values);
$params['contact_type'] = $this->getContactType();
if ($this->getContactSubType()) {
$params['contact_sub_type'] = $this->getContactSubType();
}
return $params;
}

Expand Down Expand Up @@ -2822,16 +2792,8 @@ public function validateValues(array $values): void {
//date-format part ends

$errorMessage = NULL;

//CRM-5125
//add custom fields for contact sub type
$csType = NULL;
if (!empty($this->_contactSubType)) {
$csType = $this->_contactSubType;
}

//checking error in custom data
$this->isErrorInCustomData($params, $errorMessage, $csType, $this->_relationships);
$this->isErrorInCustomData($params, $errorMessage, $params['contact_sub_type'] ?? NULL, $this->_relationships);

//checking error in core data
$this->isErrorInCoreData($params, $errorMessage);
Expand Down
14 changes: 10 additions & 4 deletions CRM/Import/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1186,8 +1186,9 @@ protected function validateRequiredFields(array $requiredFields, array $params,
* @throws \API_Exception
*/
protected function getTransformedFieldValue(string $fieldName, $importedValue) {
// For now only do gender_id as we need to work through removing duplicate handling
if ($fieldName !== 'gender_id' || empty($importedValue)) {
// For now only do gender_id etc as we need to work through removing duplicate handling
$checkedFields = ['gender_id', 'contact_sub_type'];
if (empty($importedValue) || !in_array($fieldName, $checkedFields, TRUE)) {
return $importedValue;
}
return $this->getFieldOptions($fieldName)[$importedValue] ?? 'invalid_import_value';
Expand All @@ -1209,12 +1210,17 @@ protected function getFieldOptions(string $fieldName) {
*
* @param string $fieldName
* @param bool $loadOptions
* @param bool $limitToContactType
* Only show fields for the type to import (not appropriate when looking up
* related contact fields).
*
*
* @return array
* @throws \API_Exception
* @throws \Civi\API\Exception\NotImplementedException
*/
protected function getFieldMetadata(string $fieldName, bool $loadOptions = FALSE): array {
$fieldMetadata = $this->getImportableFieldsMetadata()[$fieldName];
protected function getFieldMetadata(string $fieldName, bool $loadOptions = FALSE, $limitToContactType = FALSE): array {
$fieldMetadata = $this->getImportableFieldsMetadata()[$fieldName] ?? ($limitToContactType ? NULL : CRM_Contact_BAO_Contact::importableFields('All')[$fieldName]);
if ($loadOptions && !isset($fieldMetadata['options'])) {
if (empty($fieldMetadata['pseudoconstant'])) {
$this->importableFieldsMetadata[$fieldName]['options'] = FALSE;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
first_name,Last Name,Organization,Contact Subtype,Organization SubType,expected
Joe,Green,Greenfingers,baby,,Valid
Joe,Green,Greenfingers,Infant,,Valid
Joe,Green,Greenfingers,infant,,Valid
Joe,Green,Greenfingers,rando,,Invalid
Joe,Green,Greenfingers,,baby,Invalid
Joe,Green,Greenfingers,,Infant,Invalid
Joe,Green,Greenfingers,,infant,Invalid
80 changes: 63 additions & 17 deletions tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

use Civi\Api4\Contact;
use Civi\Api4\ContactType;
use Civi\Api4\RelationshipType;
use Civi\Api4\UserJob;

Expand All @@ -40,6 +41,7 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
public function tearDown(): void {
$this->quickCleanup(['civicrm_address', 'civicrm_phone', 'civicrm_email', 'civicrm_user_job', 'civicrm_relationship'], TRUE);
RelationshipType::delete()->addWhere('name_a_b', '=', 'Dad to')->execute();
ContactType::delete()->addWhere('name', '=', 'baby')->execute();
parent::tearDown();
}

Expand Down Expand Up @@ -986,7 +988,7 @@ public function importDataProvider(): array {
'csv' => 'individual_invalid_contact_sub_type.csv',
'mapper' => [['first_name'], ['last_name'], ['contact_sub_type']],
'expected_error' => '',
'expected_outcomes' => [CRM_Import_Parser::NO_MATCH => 1],
'expected_outcomes' => [CRM_Import_Parser::ERROR => 1],
],
];
}
Expand All @@ -1010,22 +1012,8 @@ public function testImportGenders(): void {
['do_not_import'],
];
$csv = 'individual_genders.csv';
/* @var CRM_Import_DataSource_CSV $dataSource */
/* @var \CRM_Contact_Import_Parser_Contact $parser */
[$dataSource, $parser] = $this->getDataSourceAndParser($csv, $mapper, []);
while ($values = $dataSource->getRow()) {
try {
$parser->validateValues(array_values($values));
if ($values['expected'] !== 'Valid') {
$this->fail($values['gender'] . ' should not have been valid');
}
}
catch (CRM_Core_Exception $e) {
if ($values['expected'] !== 'Invalid') {
$this->fail($values['gender'] . ' should have been valid');
}
}
}
$field = 'gender';
$this->validateMultiRowCsv($csv, $mapper, $field);

$this->importCSV($csv, $mapper);
$contacts = Contact::get()
Expand All @@ -1037,6 +1025,36 @@ public function testImportGenders(): void {
$this->assertCount(8, $contacts);
}

/**
* @throws \API_Exception
*/
public function testImportContactSubTypes(): void {
ContactType::create()->setValues([
'name' => 'baby',
'label' => 'Infant',
'parent_id:name' => 'Individual',
])->execute();
$mapper = [
['first_name'],
['last_name'],
['5_a_b', 'organization_name'],
['contact_sub_type'],
['5_a_b', 'contact_sub_type'],
];
$csv = 'individual_contact_sub_types.csv';
$field = 'contact_sub_type';

$this->validateMultiRowCsv($csv, $mapper, $field);
$this->importCSV($csv, $mapper);
$contacts = Contact::get()
->addWhere('last_name', '=', 'Green')
->addSelect('contact_sub_type:name')->execute();
foreach ($contacts as $contact) {
$this->assertEquals(['baby'], $contact['contact_sub_type:name']);
}
$this->assertCount(3, $contacts);
}

/**
* Test that setting duplicate action to fill doesn't blow away data
* that exists, but does fill in where it's empty.
Expand Down Expand Up @@ -1519,4 +1537,32 @@ protected function importCSV(string $csv, array $mapper, array $submittedValues
$form->postProcess();
}

/**
* @param string $csv
* @param array $mapper
* @param string $field
*
* @throws \API_Exception
* @throws \CRM_Core_Exception
* @throws \Civi\API\Exception\UnauthorizedException
*/
private function validateMultiRowCsv(string $csv, array $mapper, string $field): void {
/* @var CRM_Import_DataSource_CSV $dataSource */
/* @var \CRM_Contact_Import_Parser_Contact $parser */
[$dataSource, $parser] = $this->getDataSourceAndParser($csv, $mapper, []);
while ($values = $dataSource->getRow()) {
try {
$parser->validateValues(array_values($values));
if ($values['expected'] !== 'Valid') {
$this->fail($values[$field] . ' should not have been valid');
}
}
catch (CRM_Core_Exception $e) {
if ($values['expected'] !== 'Invalid') {
$this->fail($values[$field] . ' should have been valid');
}
}
}
}

}

0 comments on commit 5f6ad41

Please sign in to comment.