From 99308da45cc12426737f96ddddb65a036d6ad650 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Mon, 6 Jun 2022 16:52:56 +1200 Subject: [PATCH] Fix Participant import, add tests --- CRM/Event/BAO/Participant.php | 2 + CRM/Event/Import/Form/MapField.php | 250 ++++------ CRM/Event/Import/Form/Preview.php | 69 +-- CRM/Event/Import/Form/Summary.php | 61 --- CRM/Event/Import/Parser/Participant.php | 442 ++++-------------- CRM/Import/Form/MapField.php | 20 +- CRM/Import/Parser.php | 41 +- CRM/Upgrade/Incremental/php/FiveFiftyOne.php | 33 ++ templates/CRM/Event/Import/Form/MapField.tpl | 2 +- templates/CRM/Event/Import/Form/Preview.tpl | 2 +- .../Event/Import/Parser/ParticipantTest.php | 172 +++++++ .../Event/Import/Parser/data/participant.csv | 2 + tests/phpunit/CiviTest/CiviUnitTestCase.php | 12 + 13 files changed, 442 insertions(+), 666 deletions(-) create mode 100644 tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php create mode 100644 tests/phpunit/CRM/Event/Import/Parser/data/participant.csv diff --git a/CRM/Event/BAO/Participant.php b/CRM/Event/BAO/Participant.php index 858f4aca55..90fbc73a15 100644 --- a/CRM/Event/BAO/Participant.php +++ b/CRM/Event/BAO/Participant.php @@ -608,6 +608,7 @@ INNER JOIN civicrm_price_field field ON ( value.price_field_id = field.id // Split status and status id into 2 fields // Fixme: it would be better to leave as 1 field and intelligently handle both during import + // note import undoes this - it is still here in case the search usage uses it. $participantStatus = [ 'participant_status' => [ 'title' => ts('Participant Status'), @@ -619,6 +620,7 @@ INNER JOIN civicrm_price_field field ON ( value.price_field_id = field.id // Split role and role id into 2 fields // Fixme: it would be better to leave as 1 field and intelligently handle both during import + // note import undoes this - it is still here in case the search usage uses it. $participantRole = [ 'participant_role' => [ 'title' => ts('Participant Role'), diff --git a/CRM/Event/Import/Form/MapField.php b/CRM/Event/Import/Form/MapField.php index 33228af045..be0a9f15c1 100644 --- a/CRM/Event/Import/Form/MapField.php +++ b/CRM/Event/Import/Form/MapField.php @@ -15,6 +15,8 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\MappingField; + /** * This class gets the name of the file to upload */ @@ -26,55 +28,26 @@ class CRM_Event_Import_Form_MapField extends CRM_Import_Form_MapField { * @return void */ public function preProcess() { - $this->_mapperFields = $this->get('fields'); - asort($this->_mapperFields); + parent::preProcess(); unset($this->_mapperFields['participant_is_test']); - $this->_columnCount = $this->get('columnCount'); - $this->assign('columnCount', $this->_columnCount); - $this->_dataValues = $this->get('dataValues'); - $this->assign('dataValues', $this->_dataValues); - - $skipColumnHeader = $this->controller->exportValue('DataSource', 'skipColumnHeader'); - $this->_onDuplicate = $this->get('onDuplicate'); - $highlightedFields = []; - 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); - } - if ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { - $remove = array('participant_contact_id', 'email', 'first_name', 'last_name', 'external_identifier'); - foreach ($remove as $value) { - unset($this->_mapperFields[$value]); - } - $highlightedFieldsArray = array('participant_id', 'event_id', 'event_title', 'participant_status_id'); - foreach ($highlightedFieldsArray as $name) { - $highlightedFields[] = $name; - } - } - elseif ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP || - $this->_onDuplicate == CRM_Import_Parser::DUPLICATE_NOCHECK - ) { - unset($this->_mapperFields['participant_id']); - $highlightedFieldsArray = array( + if ($this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_UPDATE) { + $remove = [ 'participant_contact_id', - 'event_id', 'email', 'first_name', 'last_name', 'external_identifier', - 'participant_status_id', - ); - foreach ($highlightedFieldsArray as $name) { - $highlightedFields[] = $name; + ]; + foreach ($remove as $value) { + unset($this->_mapperFields[$value]); } } - $this->assign('highlightedFields', $highlightedFields); + elseif ( + $this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_SKIP + || $this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_NOCHECK) { + unset($this->_mapperFields['participant_id']); + } } /** @@ -85,23 +58,14 @@ class CRM_Event_Import_Form_MapField extends CRM_Import_Form_MapField { public function buildQuickForm() { //to save the current mappings - if (!$this->get('savedMapping')) { + if (!$this->getSubmittedValue('savedMapping')) { $saveDetailsName = ts('Save this field mapping'); $this->applyFilter('saveMappingName', 'trim'); $this->add('text', 'saveMappingName', ts('Name')); $this->add('text', 'saveMappingDesc', ts('Description')); } else { - $savedMapping = $this->get('savedMapping'); - - list($mappingName, $mappingContactType, $mappingLocation, $mappingPhoneType, $mappingRelation) = CRM_Core_BAO_Mapping::getMappingFields($savedMapping); - - $mappingName = $mappingName[1]; - $mappingContactType = $mappingContactType[1]; - $mappingLocation = $mappingLocation['1'] ?? NULL; - $mappingPhoneType = $mappingPhoneType['1'] ?? NULL; - $mappingRelation = $mappingRelation['1'] ?? NULL; - + $savedMapping = $this->getSubmittedValue('savedMapping'); //mapping is to be loaded from database $this->set('loadedMapping', $savedMapping); @@ -131,8 +95,18 @@ class CRM_Event_Import_Form_MapField extends CRM_Import_Form_MapField { $defaults = []; $mapperKeys = array_keys($this->_mapperFields); $hasHeaders = !empty($this->_columnHeaders); - $headerPatterns = $this->get('headerPatterns'); - $dataPatterns = $this->get('dataPatterns'); + $headerPatterns = $this->getHeaderPatterns(); + $dataPatterns = $this->getDataPatterns(); + $savedMappingID = $this->getSubmittedValue('savedMapping'); + //used to warn for mismatch column count or mismatch mapping + $warning = 0; + if ($savedMappingID) { + $fieldMappings = MappingField::get(FALSE)->addWhere('mapping_id', '=', $savedMappingID)->execute()->indexBy('column_number'); + //set warning if mismatch in more than + if (($this->_columnCount != count($fieldMappings))) { + $warning++; + } + } /* Initialize all field usages to false */ foreach ($mapperKeys as $key) { @@ -141,35 +115,18 @@ class CRM_Event_Import_Form_MapField extends CRM_Import_Form_MapField { $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); $sel1 = $this->_mapperFields; - $sel2[''] = NULL; $js = "\n"; $this->assign('initHideBoxes', $js); - //set warning if mismatch in more than - if (isset($mappingName)) { - if (($this->_columnCount != count($mappingName))) { - $warning++; - } - } - if ($warning != 0 && $this->get('savedMapping')) { + if ($warning != 0 && $this->getSubmittedValue('savedMapping')) { $session = CRM_Core_Session::singleton(); $session->setStatus(ts('The data columns in this import file appear to be different from the saved mapping. Please verify that you have selected the correct saved mapping before continuing.')); } @@ -283,13 +234,7 @@ class CRM_Event_Import_Form_MapField extends CRM_Import_Form_MapField { 'event_id' => ts('Event ID'), ); - $contactTypeId = $self->get('contactType'); - $contactTypes = array( - CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual', - CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household', - CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization', - ); - $contactFieldsBelowWeightMessage = self::validateRequiredContactMatchFields($contactTypes[$contactTypeId], $importKeys); + $contactFieldsBelowWeightMessage = self::validateRequiredContactMatchFields($self->getContactType(), $importKeys); foreach ($requiredFields as $field => $title) { if (!in_array($field, $importKeys)) { @@ -351,95 +296,62 @@ class CRM_Event_Import_Form_MapField extends CRM_Import_Form_MapField { } /** - * Process the mapped fields and map it into the uploaded file - * preview the file and extract some summary statistics - * - * @return void + * @return CRM_Event_Import_Parser_Participant */ - public function postProcess() { - $params = $this->controller->exportValues('MapField'); - //reload the mapfield if load mapping is pressed - if (!empty($params['savedMapping'])) { - $this->set('savedMapping', $params['savedMapping']); - $this->controller->resetPage($this->_name); - return; - } - - $mapper = []; - $mapperKeys = $this->controller->exportValue($this->_name, 'mapper'); - $mapperKeysMain = []; - - for ($i = 0; $i < $this->_columnCount; $i++) { - $mapper[$i] = $this->_mapperFields[$mapperKeys[$i][0]]; - $mapperKeysMain[$i] = $mapperKeys[$i][0]; + protected function getParser(): CRM_Event_Import_Parser_Participant { + if (!$this->parser) { + $this->parser = new CRM_Event_Import_Parser_Participant(); + $this->parser->setUserJobID($this->getUserJobID()); + $this->parser->init(); } + return $this->parser; + } - $this->set('mapper', $mapper); - - // store mapping Id to display it in the preview page - $this->set('loadMappingId', CRM_Utils_Array::value('mappingId', $params)); - - //Updating Mapping Records - if (!empty($params['updateMapping'])) { - - $mappingFields = new CRM_Core_DAO_MappingField(); - $mappingFields->mapping_id = $params['mappingId']; - $mappingFields->find(); - - $mappingFieldsId = []; - while ($mappingFields->fetch()) { - if ($mappingFields->id) { - $mappingFieldsId[$mappingFields->column_number] = $mappingFields->id; - } - } - - for ($i = 0; $i < $this->_columnCount; $i++) { - $updateMappingFields = new CRM_Core_DAO_MappingField(); - $updateMappingFields->id = $mappingFieldsId[$i]; - $updateMappingFields->mapping_id = $params['mappingId']; - $updateMappingFields->column_number = $i; - $updateMappingFields->name = $mapper[$i]; - $updateMappingFields->save(); + /** + * Get the fields to highlight. + * + * @return array + * @throws \CRM_Core_Exception + */ + protected function getHighlightedFields(): array { + $highlightedFields = []; + if ($this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_UPDATE) { + $highlightedFieldsArray = [ + 'participant_id', + 'event_id', + 'event_title', + 'participant_status_id', + ]; + foreach ($highlightedFieldsArray as $name) { + $highlightedFields[] = $name; } } - - //Saving Mapping Details and Records - if (!empty($params['saveMapping'])) { - $mappingParams = array( - 'name' => $params['saveMappingName'], - 'description' => $params['saveMappingDesc'], - 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Participant'), - ); - $saveMapping = CRM_Core_BAO_Mapping::add($mappingParams); - - for ($i = 0; $i < $this->_columnCount; $i++) { - $saveMappingFields = new CRM_Core_DAO_MappingField(); - $saveMappingFields->mapping_id = $saveMapping->id; - $saveMappingFields->column_number = $i; - $saveMappingFields->name = $mapper[$i]; - $saveMappingFields->save(); + elseif ($this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_SKIP || + $this->getSubmittedValue('onDuplicate') == CRM_Import_Parser::DUPLICATE_NOCHECK + ) { + $highlightedFieldsArray = [ + 'participant_contact_id', + 'event_id', + 'email', + 'first_name', + 'last_name', + 'external_identifier', + 'participant_status_id', + ]; + foreach ($highlightedFieldsArray as $name) { + $highlightedFields[] = $name; } - $this->set('savedMapping', $saveMappingFields->mapping_id); } - - $parser = new CRM_Event_Import_Parser_Participant($mapperKeysMain); - $parser->run($this->getSubmittedValue('uploadFile'), $this->getSubmittedValue('fieldSeparator'), $mapper, $this->getSubmittedValue('skipColumnHeader'), - CRM_Import_Parser::MODE_PREVIEW, $this->get('contactType') - ); - // add all the necessary variables to the form - $parser->set($this); + return $highlightedFields; } /** - * @return CRM_Event_Import_Parser_Participant + * Get the mapping name per the civicrm_mapping_field.type_id option group. + * + * @return string */ - protected function getParser(): CRM_Event_Import_Parser_Participant { - if (!$this->parser) { - $this->parser = new CRM_Event_Import_Parser_Participant(); - $this->parser->setUserJobID($this->getUserJobID()); - $this->parser->init(); - } - return $this->parser; + public function getMappingTypeName(): string { + return 'Import Participant'; } } diff --git a/CRM/Event/Import/Form/Preview.php b/CRM/Event/Import/Form/Preview.php index f099472742..d287c1528b 100644 --- a/CRM/Event/Import/Form/Preview.php +++ b/CRM/Event/Import/Form/Preview.php @@ -21,48 +21,6 @@ */ class CRM_Event_Import_Form_Preview extends CRM_Import_Form_Preview { - /** - * Set variables up before form is built. - * - * @return void - */ - public function preProcess() { - parent::preProcess(); - - //get the data from the session - $dataValues = $this->get('dataValues'); - $mapper = $this->get('mapper'); - $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); - - if ($invalidRowCount) { - $urlParams = 'type=' . CRM_Import_Parser::ERROR . '&parser=CRM_Event_Import_Parser_Participant'; - $this->set('downloadErrorRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams)); - } - - $properties = [ - 'mapper', - 'dataValues', - 'columnCount', - 'totalRowCount', - 'validRowCount', - 'invalidRowCount', - 'downloadErrorRecordsUrl', - ]; - - foreach ($properties as $property) { - $this->assign($property, $this->get($property)); - } - } - /** * Process the mapped fields and map it into the uploaded file * preview the file and extract some summary statistics @@ -96,36 +54,11 @@ class CRM_Event_Import_Form_Preview extends CRM_Import_Form_Preview { $parser->run($fileName, $separator, $mapperFields, $this->getSubmittedValue('skipColumnHeader'), - CRM_Import_Parser::MODE_IMPORT, - $this->get('contactType'), - $onDuplicate + CRM_Import_Parser::MODE_IMPORT ); // add all the necessary variables to the form $parser->set($this, CRM_Import_Parser::MODE_IMPORT); - - // check if there is any error occurred - - $errorStack = CRM_Core_Error::singleton(); - $errors = $errorStack->getErrors(); - $errorMessage = []; - - if (is_array($errors)) { - foreach ($errors as $key => $value) { - $errorMessage[] = $value['message']; - } - - $errorFile = $fileName['name'] . '.error.log'; - - if ($fd = fopen($errorFile, 'w')) { - fwrite($fd, implode('\n', $errorMessage)); - } - fclose($fd); - - $this->set('errorFile', $errorFile); - $urlParams = 'type=' . CRM_Import_Parser::ERROR . '&parser=CRM_Event_Import_Parser_Participant'; - $this->set('downloadErrorRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams)); - } } /** diff --git a/CRM/Event/Import/Form/Summary.php b/CRM/Event/Import/Form/Summary.php index 3d26c21fbe..024c735f22 100644 --- a/CRM/Event/Import/Form/Summary.php +++ b/CRM/Event/Import/Form/Summary.php @@ -20,65 +20,4 @@ */ class CRM_Event_Import_Form_Summary extends CRM_Import_Form_Summary { - /** - * Set variables up before form is built. - * - * @return void - */ - public function preProcess() { - // set the error message path to display - $this->assign('errorFile', $this->get('errorFile')); - - $totalRowCount = $this->get('totalRowCount'); - $this->set('totalRowCount', $totalRowCount); - - $invalidRowCount = $this->get('invalidRowCount'); - $duplicateRowCount = $this->get('duplicateRowCount'); - $onDuplicate = $this->get('onDuplicate'); - if ($duplicateRowCount > 0) { - $urlParams = 'type=' . CRM_Import_Parser::DUPLICATE . '&parser=CRM_Event_Import_Parser_Participant'; - $this->set('downloadDuplicateRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams)); - } - else { - $duplicateRowCount = 0; - $this->set('duplicateRowCount', $duplicateRowCount); - } - - $this->assign('dupeError', FALSE); - - if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { - $dupeActionString = ts('These records have been updated with the imported data.'); - } - elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_FILL) { - $dupeActionString = ts('These records have been filled in with the imported data.'); - } - else { - /* Skip by default */ - - $dupeActionString = ts('These records have not been imported.'); - - $this->assign('dupeError', TRUE); - - /* only subtract dupes from successful import if we're skipping */ - - $this->set('validRowCount', $totalRowCount - $invalidRowCount - - $duplicateRowCount - ); - } - $this->assign('dupeActionString', $dupeActionString); - - $properties = [ - 'totalRowCount', - 'validRowCount', - 'invalidRowCount', - 'downloadErrorRecordsUrl', - 'duplicateRowCount', - 'downloadDuplicateRecordsUrl', - 'groupAdditions', - ]; - foreach ($properties as $property) { - $this->assign($property, $this->get($property)); - } - } - } diff --git a/CRM/Event/Import/Parser/Participant.php b/CRM/Event/Import/Parser/Participant.php index 826d19b870..90c074167a 100644 --- a/CRM/Event/Import/Parser/Participant.php +++ b/CRM/Event/Import/Parser/Participant.php @@ -23,12 +23,6 @@ require_once 'CRM/Utils/DeprecatedUtils.php'; class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { protected $_mapperKeys; - private $_contactIdIndex; - private $_eventIndex; - private $_participantStatusIndex; - private $_participantRoleIndex; - private $_eventTitleIndex; - /** * Array of successfully imported participants id's * @@ -81,55 +75,28 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { * The initializer code, called before the processing. */ public function init() { - $fields = CRM_Event_BAO_Participant::importableFields($this->_contactType, FALSE); - $fields['event_id']['title'] = 'Event ID'; - $eventfields = &CRM_Event_BAO_Event::fields(); - $fields['event_title'] = $eventfields['event_title']; - - foreach ($fields as $name => $field) { + $this->setFieldMetadata(); + foreach ($this->importableFieldsMetadata as $name => $field) { $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT); $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//'); $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//'); $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']); } + } - $this->_newParticipants = []; - $this->setActiveFields($this->_mapperKeys); - - // FIXME: we should do this in one place together with Form/MapField.php - $this->_contactIdIndex = -1; - $this->_eventIndex = -1; - $this->_participantStatusIndex = -1; - $this->_participantRoleIndex = -1; - $this->_eventTitleIndex = -1; - - $index = 0; - foreach ($this->_mapperKeys as $key) { - - switch ($key) { - case 'participant_contact_id': - $this->_contactIdIndex = $index; - break; - - case 'event_id': - $this->_eventIndex = $index; - break; - - case 'participant_status': - case 'participant_status_id': - $this->_participantStatusIndex = $index; - break; - - case 'participant_role_id': - $this->_participantRoleIndex = $index; - break; - - case 'event_title': - $this->_eventTitleIndex = $index; - break; - } - $index++; - } + /** + * Get the metadata field for which importable fields does not key the actual field name. + * + * @return string[] + */ + protected function getOddlyMappedMetadataFields(): array { + $uniqueNames = ['participant_id', 'participant_campaign_id', 'participant_contact_id', 'participant_status_id', 'participant_role_id', 'participant_register_date', 'participant_source', 'participant_is_pay_later']; + $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()); } /** @@ -145,6 +112,21 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { return $this->summary($values); } + /** + * Validate the values. + * + * @param array $values + * The array of values belonging to this line. + * + * @return bool + * the result of this processing + */ + public function validateValues($values) { + $params = $this->getMappedRow($values); + $this->validateParams($params); + return CRM_Import_Parser::VALID; + } + /** * Handle the values in summary mode. * @@ -155,105 +137,18 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { * the result of this processing */ public function summary(&$values) { - $this->setActiveFieldValues($values); - $index = -1; - - if ($this->_eventIndex > -1 && $this->_eventTitleIndex > -1) { - array_unshift($values, ts('Select either EventID OR Event Title')); - return CRM_Import_Parser::ERROR; - } - elseif ($this->_eventTitleIndex > -1) { - $index = $this->_eventTitleIndex; - } - elseif ($this->_eventIndex > -1) { - $index = $this->_eventIndex; - } - $params = &$this->getActiveFieldParams(); - - if (!(($index < 0) || ($this->_participantStatusIndex < 0))) { - $errorRequired = !CRM_Utils_Array::value($this->_participantStatusIndex, $values); - if (empty($params['event_id']) && empty($params['event_title'])) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Event', $missingField); - } - if (empty($params['participant_status_id'])) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Participant Status', $missingField); - } - } - else { - $errorRequired = TRUE; - $missingField = NULL; - if ($index < 0) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Event', $missingField); - } - if ($this->_participantStatusIndex < 0) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Participant Status', $missingField); - } - } - - if ($errorRequired) { - array_unshift($values, ts('Missing required field(s) :') . $missingField); - return CRM_Import_Parser::ERROR; + $params = $this->getMappedRow($values); + $errors = []; + try { + $this->validateParams($params); } - - $errorMessage = NULL; - - //for date-Formats - $session = CRM_Core_Session::singleton(); - $dateType = $session->get('dateTypes'); - - foreach ($params as $key => $val) { - if ($val && ($key == 'participant_register_date')) { - if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { - $params[$key] = $dateValue; - } - else { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Register Date', $errorMessage); - } - } - elseif ($val && ($key == 'participant_role_id' || $key == 'participant_role')) { - $roleIDs = CRM_Event_PseudoConstant::participantRole(); - $val = explode(',', $val); - if ($key == 'participant_role_id') { - foreach ($val as $role) { - if (!array_key_exists(trim($role), $roleIDs)) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Participant Role Id', $errorMessage); - break; - } - } - } - else { - foreach ($val as $role) { - if (!$this->in_value(trim($role), $roleIDs)) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Participant Role', $errorMessage); - break; - } - } - } - } - elseif ($val && (($key == 'participant_status_id') || ($key == 'participant_status'))) { - $statusIDs = CRM_Event_PseudoConstant::participantStatus(); - if ($key == 'participant_status_id') { - if (!array_key_exists(trim($val), $statusIDs)) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Participant Status Id', $errorMessage); - break; - } - } - elseif (!$this->in_value($val, $statusIDs)) { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Participant Status', $errorMessage); - break; - } - } + catch (CRM_Core_Exception $e) { + $errors[] = $e->getMessage(); } - //date-Format part ends - $params['contact_type'] = 'Participant'; - //checking error in custom data - $this->isErrorInCustomData($params, $errorMessage); - - if ($errorMessage) { - $tempMsg = "Invalid value for field(s) : $errorMessage"; + if ($errors) { + $tempMsg = "Invalid value for field(s) : " . implode(',', $errors); array_unshift($values, $tempMsg); - $errorMessage = NULL; return CRM_Import_Parser::ERROR; } return CRM_Import_Parser::VALID; @@ -262,48 +157,21 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { /** * 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 bool * the result of this processing */ - public function import($onDuplicate, &$values) { + public function import(&$values) { + $rowNumber = (int) ($values[array_key_last($values)]); try { - // first make sure this is a valid line - $response = $this->summary($values); - if ($response != CRM_Import_Parser::VALID) { - return $response; - } - $params = &$this->getActiveFieldParams(); + $params = $this->getMappedRow($values); $session = CRM_Core_Session::singleton(); - $dateType = $session->get('dateTypes'); - $formatted = ['version' => 3]; - $customFields = CRM_Core_BAO_CustomField::getFields('Participant'); - + $formatted = $params; // don't add to recent items, CRM-4399 $formatted['skipRecentView'] = TRUE; - foreach ($params as $key => $val) { - if ($val) { - if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) { - if ($customFields[$customFieldID]['data_type'] == 'Date') { - $this->formatCustomDate($params, $formatted, $dateType, $key); - unset($params[$key]); - } - elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') { - $params[$key] = CRM_Utils_String::strtoboolstr($val); - } - } - if ($key == 'participant_register_date') { - CRM_Utils_Date::convertToDefaultDate($params, $dateType, 'participant_register_date'); - $formatted['participant_register_date'] = CRM_Utils_Date::processDate($params['participant_register_date']); - } - } - } - if (!(!empty($params['participant_role_id']) || !empty($params['participant_role']))) { if (!empty($params['event_id'])) { $params['participant_role_id'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $params['event_id'], 'default_role_id'); @@ -328,23 +196,17 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { $formatError = $this->formatValues($formatted, $formatValues); if ($formatError) { - array_unshift($values, $formatError['error_message']); - return CRM_Import_Parser::ERROR; - } - - if (!CRM_Utils_Rule::integer($formatted['event_id'])) { - array_unshift($values, ts('Invalid value for Event ID')); - return CRM_Import_Parser::ERROR; + throw new CRM_Core_Exception($formatError['error_message']); } - if ($onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE) { + if (!$this->isUpdateExisting()) { $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted, NULL, 'Participant' ); } else { - if ($formatValues['participant_id']) { + if (!empty($formatValues['participant_id'])) { $dao = new CRM_Event_BAO_Participant(); $dao->id = $formatValues['participant_id']; @@ -361,8 +223,7 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { //@todo calling api functions directly is not supported $newParticipant = $this->deprecated_participant_check_params($formatted, $participantValues, FALSE); if ($newParticipant['error_message']) { - array_unshift($values, $newParticipant['error_message']); - return CRM_Import_Parser::ERROR; + throw new CRM_Core_Exception($newParticipant['error_message']); } $newParticipant = CRM_Event_BAO_Participant::create($formatted, $ids); if (!empty($formatted['fee_level'])) { @@ -374,16 +235,14 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { } $this->_newParticipant[] = $newParticipant->id; + $this->setImportStatus($rowNumber, 'IMPORTED', '', $newParticipant->id); return CRM_Import_Parser::VALID; } - else { - array_unshift($values, 'Matching Participant record not found for Participant ID ' . $formatValues['participant_id'] . '. Row was skipped.'); - return CRM_Import_Parser::ERROR; - } + throw new CRM_Core_Exception('Matching Participant record not found for Participant ID ' . $formatValues['participant_id'] . '. Row was skipped.'); } } - if ($this->_contactIdIndex < 0) { + if (empty($params['contact_id'])) { $error = $this->checkContactDuplicate($formatValues); if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) { @@ -392,7 +251,7 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { foreach ($matchedIDs as $contactId) { $formatted['contact_id'] = $contactId; $formatted['version'] = 3; - $newParticipant = $this->deprecated_create_participant_formatted($formatted, $onDuplicate); + $newParticipant = $this->deprecated_create_participant_formatted($formatted); } } } @@ -425,9 +284,7 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { $disp = $params['external_identifier']; } } - - array_unshift($values, 'No matching Contact found for (' . $disp . ')'); - return CRM_Import_Parser::ERROR; + throw new CRM_Core_Exception('No matching Contact found for (' . $disp . ')'); } } else { @@ -436,16 +293,14 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { $checkCid->external_identifier = $formatValues['external_identifier']; $checkCid->find(TRUE); if ($checkCid->id != $formatted['contact_id']) { - array_unshift($values, 'Mismatch of External ID:' . $formatValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']); - return CRM_Import_Parser::ERROR; + throw new CRM_Core_Exception('Mismatch of External ID:' . $formatValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']); } } - - $newParticipant = $this->deprecated_create_participant_formatted($formatted, $onDuplicate); + $newParticipant = $this->deprecated_create_participant_formatted($formatted); } if (is_array($newParticipant) && civicrm_error($newParticipant)) { - if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) { + if ($this->isSkipDuplicates()) { $contactID = $newParticipant['contactID'] ?? NULL; $participantID = $newParticipant['participantID'] ?? NULL; @@ -456,12 +311,13 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { ($participantID == $newParticipant['error_message']['params'][0]) ) { array_unshift($values, $url); + $this->setImportStatus($rowNumber, 'DUPLICATE', ''); return CRM_Import_Parser::DUPLICATE; } if ($newParticipant['error_message']) { - throw new CRM_Core_Exception($newParticipant['error_message'], CRM_Import_Parser::ERROR); + throw new CRM_Core_Exception($newParticipant['error_message']); } - throw new CRM_Core_Exception('', CRM_Import_Parser::ERROR); + throw new CRM_Core_Exception(ts('Unknown error')); } } @@ -471,10 +327,15 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { } catch (CRM_Core_Exception $e) { array_unshift($values, $e->getMessage()); + $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage()); return CRM_Import_Parser::ERROR; } - - return CRM_Import_Parser::VALID; + catch (CiviCRM_API3_Exception $e) { + array_unshift($values, $e->getMessage()); + $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage()); + return CRM_Import_Parser::ERROR; + } + $this->setImportStatus($rowNumber, 'IMPORTED', ''); } /** @@ -547,23 +408,6 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { } break; - case 'event_title': - $id = CRM_Core_DAO::getFieldValue("CRM_Event_DAO_Event", $value, 'id', 'title'); - $values['event_id'] = $id; - break; - - case 'event_id': - if (!CRM_Utils_Rule::integer($value)) { - return civicrm_api3_create_error("Event ID is not valid: $value"); - } - $svq = CRM_Core_DAO::singleValueQuery('SELECT id FROM civicrm_event WHERE id = %1', [ - 1 => [$value, 'Integer'], - ]); - if (!$svq) { - return civicrm_api3_create_error("Invalid Event ID: There is no event record with event_id = $value."); - } - break; - case 'participant_status_id': if (!CRM_Utils_Rule::integer($value)) { return civicrm_api3_create_error("Event Status ID is not valid: $value"); @@ -627,7 +471,6 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { /** * @param array $params - * @param $onDuplicate * * @return array|bool * @@ -636,8 +479,8 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { * moved on out * */ - protected function deprecated_create_participant_formatted($params, $onDuplicate) { - if ($onDuplicate != CRM_Import_Parser::DUPLICATE_NOCHECK) { + protected function deprecated_create_participant_formatted($params) { + if ($this->isIgnoreDuplicates()) { CRM_Core_Error::reset(); $error = $this->deprecated_participant_check_params($params, TRUE); if (civicrm_error($error)) { @@ -711,8 +554,6 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { * @param $mapper * @param bool $skipColumnHeader * @param int $mode - * @param int $contactType - * @param int $onDuplicate * * @return mixed * @throws Exception @@ -722,28 +563,13 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { $separator, $mapper, $skipColumnHeader = FALSE, - $mode = self::MODE_PREVIEW, - $contactType = self::CONTACT_INDIVIDUAL, - $onDuplicate = self::DUPLICATE_SKIP + $mode = self::MODE_PREVIEW ) { 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->getContactType(); $this->init(); $this->_haveColumnHeader = $skipColumnHeader; @@ -771,32 +597,11 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { $this->_activeFieldCount = count($this->_activeFields); } - while (!feof($fd)) { + $dataSource = $this->getDataSourceObject(); + $dataSource->setStatuses(['new']); + while ($row = $dataSource->getRow()) { $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; - } + $values = array_values($row); $this->_totalCount++; @@ -810,79 +615,7 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { $returnCode = $this->summary($values); } elseif ($mode == self::MODE_IMPORT) { - $returnCode = $this->import($onDuplicate, $values); - } - 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::ERROR) { - $this->_invalidRowCount++; - $recordNumber = $this->_lineCount; - if ($this->_haveColumnHeader) { - $recordNumber--; - } - array_unshift($values, $recordNumber); - $this->_errors[] = $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('Participant'); - 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->_duplicateCount) { - $headers = array_merge([ - ts('Line Number'), - ts('View Participant URL'), - ], $customHeaders); - - $this->_duplicateFileName = self::errorFileName(self::DUPLICATE); - self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates); + $returnCode = $this->import($values); } } } @@ -1024,21 +757,24 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { } /** - * Check a value present or not in a array. + * Set up field metadata. * - * @param $value - * @param $valueArray - * - * @return bool + * @return void */ - protected function in_value($value, $valueArray) { - foreach ($valueArray as $key => $v) { - //fix for CRM-1514 - if (strtolower(trim($v, ".")) == strtolower(trim($value, "."))) { - return TRUE; - } + protected function setFieldMetadata(): void { + if (empty($this->importableFieldsMetadata)) { + $fields = CRM_Event_BAO_Participant::importableFields($this->getContactType(), FALSE); + // We can't import event type, the other two duplicate id fields that work fine. + unset($fields['participant_role'], $fields['participant_status'], $fields['event_type']); + $this->importableFieldsMetadata = $fields; } - return FALSE; + } + + /** + * @return array + */ + protected function getRequiredFields(): array { + return [['event_id' => ts('Event'), 'status_id' => ts('Status')]]; } } diff --git a/CRM/Import/Form/MapField.php b/CRM/Import/Form/MapField.php index 26f9030778..8fb266dc4b 100644 --- a/CRM/Import/Form/MapField.php +++ b/CRM/Import/Form/MapField.php @@ -14,6 +14,8 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\MappingField; + /** * This class gets the name of the file to upload. * @@ -249,16 +251,18 @@ abstract class CRM_Import_Form_MapField extends CRM_Import_Forms { if (empty($mappedField['name'])) { $mappedField['name'] = 'do_not_import'; } - if ($isUpdate) { - Civi\Api4\MappingField::update(FALSE) - ->setValues($mappedField) - ->addWhere('column_number', '=', $columnNumber) - ->addWhere('mapping_id', '=', $mappingID) - ->execute(); + $existing = MappingField::get(FALSE) + ->addWhere('column_number', '=', $columnNumber) + ->addWhere('mapping_id', '=', $mappingID)->execute()->first(); + if (empty($existing['id'])) { + MappingField::create(FALSE) + ->setValues($mappedField)->execute(); } else { - Civi\Api4\MappingField::create(FALSE) - ->setValues($mappedField)->execute(); + MappingField::update(FALSE) + ->setValues($mappedField) + ->addWhere('id', '=', $existing['id']) + ->execute(); } } diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php index b92831e750..bd0a0d9e1f 100644 --- a/CRM/Import/Parser.php +++ b/CRM/Import/Parser.php @@ -9,7 +9,9 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\Campaign; use Civi\Api4\CustomField; +use Civi\Api4\Event; use Civi\Api4\UserJob; /** @@ -1325,7 +1327,7 @@ abstract class CRM_Import_Parser { if (!empty($fieldMetadata['serialize']) && count(explode(',', $importedValue)) > 1) { $values = []; foreach (explode(',', $importedValue) as $value) { - $values[] = $this->getTransformedFieldValue($fieldName, $value); + $values[] = $this->getTransformedFieldValue($fieldName, trim($value)); } return $values; } @@ -1365,6 +1367,24 @@ abstract class CRM_Import_Parser { $comparisonValue = is_numeric($importedValue) ? $importedValue : mb_strtolower($importedValue); return $options[$comparisonValue] ?? 'invalid_import_value'; } + if (!empty($fieldMetadata['FKClassName']) || !empty($fieldMetadata['pseudoconstant']['prefetch'])) { + // @todo - make this generic - for fields where getOptions doesn't fetch + // getOptions does not retrieve these fields with high potential results + if ($fieldName === 'event_id') { + if (!isset(Civi::$statics[__CLASS__][$fieldName][$importedValue])) { + $event = Event::get()->addWhere('title', '=', $importedValue)->addSelect('id')->execute()->first(); + Civi::$statics[__CLASS__][$fieldName][$importedValue] = $event['id'] ?? FALSE; + } + return Civi::$statics[__CLASS__][$fieldName][$importedValue] ?? 'invalid_import_value'; + } + if ($fieldMetadata['name'] === 'campaign_id') { + if (!isset(Civi::$statics[__CLASS__][$fieldName][$importedValue])) { + $campaign = Campaign::get()->addClause('OR', ['title', '=', $importedValue], ['name', '=', $importedValue])->addSelect('id')->execute()->first(); + Civi::$statics[__CLASS__][$fieldName][$importedValue] = $campaign['id'] ?? FALSE; + } + return Civi::$statics[__CLASS__][$fieldName][$importedValue] ?? 'invalid_import_value'; + } + } return $importedValue; } @@ -1606,6 +1626,8 @@ abstract class CRM_Import_Parser { /** * @param array $params + * + * @throws \CRM_Core_Exception */ protected function validateParams(array $params): void { $this->validateRequiredFields($this->getRequiredFields(), $params); @@ -1629,18 +1651,27 @@ abstract class CRM_Import_Parser { * @param string $prefixString * * @return array - * @throws \API_Exception */ protected function getInvalidValues($value, string $key = '', string $prefixString = ''): array { $errors = []; if ($value === 'invalid_import_value') { - $metadata = $this->getFieldMetadata($key); - $errors[] = $prefixString . ($metadata['html']['label'] ?? $metadata['title']); + if (!is_numeric($key)) { + $metadata = $this->getFieldMetadata($key); + $errors[] = $prefixString . ($metadata['html']['label'] ?? $metadata['title']); + } + else { + // Numeric key suggests we are drilling into option values + $errors[] = TRUE; + } } elseif (is_array($value)) { foreach ($value as $innerKey => $innerValue) { $result = $this->getInvalidValues($innerValue, $innerKey, $prefixString); - if (!empty($result)) { + if ($result === [TRUE]) { + $metadata = $this->getFieldMetadata($key); + $errors[] = $prefixString . ($metadata['html']['label'] ?? $metadata['title']); + } + elseif (!empty($result)) { $errors = array_merge($result, $errors); } } diff --git a/CRM/Upgrade/Incremental/php/FiveFiftyOne.php b/CRM/Upgrade/Incremental/php/FiveFiftyOne.php index f3a8f2dff4..1413b242b6 100644 --- a/CRM/Upgrade/Incremental/php/FiveFiftyOne.php +++ b/CRM/Upgrade/Incremental/php/FiveFiftyOne.php @@ -126,6 +126,39 @@ class CRM_Upgrade_Incremental_php_FiveFiftyOne extends CRM_Upgrade_Incremental_B } } + // Participant fields... + // Yes - I know they could be combined - but it's also less confusing this way. + $mappings = MappingField::get(FALSE) + ->setSelect(['id', 'name']) + ->addWhere('mapping_id.mapping_type_id:name', '=', 'Import Participant') + ->execute(); + + $fields = CRM_Event_BAO_Participant::importableFields('All', FALSE); + $fields['event_id']['title'] = 'Event ID'; + $eventfields = CRM_Event_BAO_Event::fields(); + $fields['event_title'] = $eventfields['event_title']; + + $fieldMap = []; + foreach ($fields as $fieldName => $field) { + $fieldMap[$field['title']] = $fieldName; + if (!empty($field['html']['label'])) { + $fieldMap[$field['html']['label']] = $fieldName; + } + } + $fieldMap[ts('- do not import -')] = 'do_not_import'; + $fieldMap[ts('Participant Status')] = 'participant_status_id'; + $fieldMap[ts('Participant Role')] = 'participant_role_id'; + $fieldMap[ts('Event Title')] = 'event_id'; + + foreach ($mappings as $mapping) { + if (!empty($fieldMap[$mapping['name']])) { + MappingField::update(FALSE) + ->addWhere('id', '=', $mapping['id']) + ->addValue('name', $fieldMap[$mapping['name']]) + ->execute(); + } + } + return TRUE; } diff --git a/templates/CRM/Event/Import/Form/MapField.tpl b/templates/CRM/Event/Import/Form/MapField.tpl index eb99c84b26..fd171bb746 100644 --- a/templates/CRM/Event/Import/Form/MapField.tpl +++ b/templates/CRM/Event/Import/Form/MapField.tpl @@ -27,7 +27,7 @@ {* Table for mapping data to CRM fields *} - {include file="CRM/Import/Form/MapTable.tpl"} + {include file="CRM/Import/Form/MapTableCommon.tpl" mapper=$form.mapper} diff --git a/templates/CRM/Event/Import/Form/Preview.tpl b/templates/CRM/Event/Import/Form/Preview.tpl index f64d3c9c5e..61f950ec9d 100644 --- a/templates/CRM/Event/Import/Form/Preview.tpl +++ b/templates/CRM/Event/Import/Form/Preview.tpl @@ -55,7 +55,7 @@ {* Table for mapping preview *} - {include file="CRM/Import/Form/MapTable.tpl"} + {include file="CRM/Import/Form/MapTableCommon.tpl"}
diff --git a/tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php b/tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php new file mode 100644 index 0000000000..1d6f1ba6c2 --- /dev/null +++ b/tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php @@ -0,0 +1,172 @@ +. + */ + +use Civi\Api4\UserJob; + +/** + * @package CiviCRM + * @group headless + */ +class CRM_Participant_Import_Parser_ParticipantTest extends CiviUnitTestCase { + + use CRMTraits_Custom_CustomDataTrait; + + protected $entity = 'Participant'; + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + public function tearDown(): void { + $this->quickCleanup([ + 'civicrm_event', + 'civicrm_participant', + 'civicrm_contact', + 'civicrm_email', + 'civicrm_user_job', + 'civicrm_queue', + 'civicrm_queue_item', + ], TRUE); + parent::tearDown(); + } + + /** + * Import the csv file values. + * + * This function uses a flow that mimics the UI flow. + * + * @param string $csv Name of csv file. + * @param array $fieldMappings + * @param array $submittedValues + */ + protected function importCSV(string $csv, array $fieldMappings, array $submittedValues = []): void { + $submittedValues = array_merge([ + 'uploadFile' => ['name' => __DIR__ . '/data/' . $csv], + 'skipColumnHeader' => TRUE, + 'fieldSeparator' => ',', + 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL, + 'mapper' => $this->getMapperFromFieldMappings($fieldMappings), + 'dataSource' => 'CRM_Import_DataSource_CSV', + 'file' => ['name' => $csv], + 'dateFormats' => CRM_Core_Form_Date::DATE_yyyy_mm_dd, + 'onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE, + 'groups' => [], + ], $submittedValues); + /* @var \CRM_Event_Import_Form_DataSource $form */ + $form = $this->getFormObject('CRM_Event_Import_Form_DataSource', $submittedValues); + $values = $_SESSION['_' . $form->controller->_name . '_container']['values']; + $form->buildForm(); + $form->postProcess(); + // This gets reset in DataSource so re-do.... + $_SESSION['_' . $form->controller->_name . '_container']['values'] = $values; + + $this->userJobID = $form->getUserJobID(); + /* @var CRM_Event_Import_Form_MapField $form */ + $form = $this->getFormObject('CRM_Event_Import_Form_MapField', $submittedValues); + $form->setUserJobID($this->userJobID); + $form->buildForm(); + $form->postProcess(); + /* @var CRM_Event_Import_Form_Preview $form */ + $form = $this->getFormObject('CRM_Event_Import_Form_Preview', $submittedValues); + $form->setUserJobID($this->userJobID); + $form->buildForm(); + $form->postProcess(); + } + + /** + * @param array $mappings + * + * @return array + */ + protected function getMapperFromFieldMappings(array $mappings): array { + $mapper = []; + foreach ($mappings as $mapping) { + $fieldInput = [$mapping['name']]; + $mapper[] = $fieldInput; + } + return $mapper; + } + + /** + * Test the full form-flow import. + */ + public function testImportCSV() :void { + $this->campaignCreate(['name' => 'Soccer cup']); + $this->eventCreate(['title' => 'Rain-forest Cup Youth Soccer Tournament']); + $this->individualCreate(['email' => 'mum@example.com']); + $this->importCSV('participant.csv', [ + ['name' => 'event_id'], + ['name' => 'participant_campaign_id'], + ['name' => 'email'], + ['name' => 'participant_fee_amount'], + ['name' => 'participant_fee_currency'], + ['name' => 'participant_fee_level'], + ['name' => 'participant_is_pay_later'], + ['name' => 'participant_role_id'], + ['name' => 'participant_source'], + ['name' => 'participant_status_id'], + ['name' => 'participant_register_date'], + ['name' => 'do_not_import'], + ]); + $dataSource = new CRM_Import_DataSource_CSV($this->userJobID); + $row = $dataSource->getRow(); + $this->assertEquals('IMPORTED', $row['_status']); + $this->callAPISuccessGetSingle('Participant', ['campaign_id' => 'Soccer Cup']); + } + + /** + * @param array $submittedValues + * + * @return int + * @noinspection PhpDocMissingThrowsInspection + */ + protected function getUserJobID(array $submittedValues = []): int { + $userJobID = UserJob::create()->setValues([ + 'metadata' => [ + 'submitted_values' => array_merge([ + 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL, + 'contactSubType' => '', + 'dataSource' => 'CRM_Import_DataSource_SQL', + 'sqlQuery' => 'SELECT first_name FROM civicrm_contact', + 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP, + 'dedupe_rule_id' => NULL, + 'dateFormats' => CRM_Core_Form_Date::DATE_yyyy_mm_dd, + ], $submittedValues), + ], + 'status_id:name' => 'draft', + 'type_id:name' => 'participant_import', + ])->execute()->first()['id']; + if ($submittedValues['dataSource'] ?? NULL === 'CRM_Import_DataSource') { + $dataSource = new CRM_Import_DataSource_CSV($userJobID); + } + else { + $dataSource = new CRM_Import_DataSource_SQL($userJobID); + } + $dataSource->initialize(); + return $userJobID; + } + +} diff --git a/tests/phpunit/CRM/Event/Import/Parser/data/participant.csv b/tests/phpunit/CRM/Event/Import/Parser/data/participant.csv new file mode 100644 index 0000000000..ff14311ff9 --- /dev/null +++ b/tests/phpunit/CRM/Event/Import/Parser/data/participant.csv @@ -0,0 +1,2 @@ +Event Title,Campaign ID,Email (match to contact),Fee Amount,Fee Currency,Fee level,Is Pay Later,Participant Role,Participant Source,Participant Status,Register date,Do not import +Rain-forest Cup Youth Soccer Tournament,Soccer Cup,mum@example.com,5000.55,USD,High,No,"Attendee, Volunteer",Phoned up,Registered,2022-12-07, diff --git a/tests/phpunit/CiviTest/CiviUnitTestCase.php b/tests/phpunit/CiviTest/CiviUnitTestCase.php index d0959aba7d..5ac375ea34 100644 --- a/tests/phpunit/CiviTest/CiviUnitTestCase.php +++ b/tests/phpunit/CiviTest/CiviUnitTestCase.php @@ -3255,6 +3255,18 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase { $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues; return $form; + case 'CRM_Event_Import_Form_DataSource': + case 'CRM_Event_Import_Form_MapField': + case 'CRM_Event_Import_Form_Preview': + $form->controller = new CRM_Event_Import_Controller(); + $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller)); + // The submitted values should be set on one or the other of the forms in the flow. + // For test simplicity we set on all rather than figuring out which ones go where.... + $_SESSION['_' . $form->controller->_name . '_container']['values']['DataSource'] = $formValues; + $_SESSION['_' . $form->controller->_name . '_container']['values']['MapField'] = $formValues; + $_SESSION['_' . $form->controller->_name . '_container']['values']['Preview'] = $formValues; + return $form; + case strpos($class, '_Form_') !== FALSE: $form->controller = new CRM_Core_Controller_Simple($class, $pageName); break; -- 2.25.1