*/
use Civi\Api4\UserJob;
+use League\Csv\Writer;
/**
* This class helps the forms within the import flow access submitted & parsed values.
*/
protected $userJob;
+ /**
+ * @var \CRM_Import_Parser
+ */
+ protected $parser;
+
/**
* Get User Job.
*
'onDuplicate' => 'DataSource',
'disableUSPS' => 'DataSource',
'doGeocodeAddress' => 'DataSource',
+ 'multipleCustomData' => 'DataSource',
// Note we don't add the save mapping instructions for MapField here
// (eg 'updateMapping') - as they really are an action for that form
// rather than part of the mapping config.
* @param string $fieldName
*
* @return mixed|null
- * @throws \CRM_Core_Exception
*/
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)) {
* This is called as a snippet in DataSourceConfig and
* also from DataSource::buildForm to add the fields such
* that quick form picks them up.
- *
- * @throws \CRM_Core_Exception
*/
protected function getDataSourceFields(): array {
$className = $this->getDataSourceClassName();
* all forms.
*
* @return string[]
- * @throws \CRM_Core_Exception
*/
protected function getSubmittableFields(): array {
$dataSourceFields = array_fill_keys($this->getDataSourceFields(), 'DataSource');
$id = UserJob::create(FALSE)
->setValues([
'created_id' => CRM_Core_Session::getLoggedInContactID(),
- 'type_id:name' => 'contact_import',
+ 'type_id:name' => $this->getUserJobType(),
'status_id:name' => 'draft',
// This suggests the data could be cleaned up after this.
'expires_date' => '+ 1 week',
* In the future we will use the dataSource object, likely
* supporting offset as well.
*
- * @param int $limit
+ * @return array|int
+ * One or more of the statues available - e.g
+ * CRM_Import_Parser::VALID
+ * or [CRM_Import_Parser::ERROR, CRM_Import_Parser::VALID]
*
- * @return array
+ * @throws \CRM_Core_Exception
+ * @throws \API_Exception
+ */
+ protected function getDataRows($statuses = [], int $limit = 0): array {
+ $statuses = (array) $statuses;
+ return $this->getDataSourceObject()->setLimit($limit)->setStatuses($statuses)->getRows();
+ }
+
+ /**
+ * Get the number of rows with the specified status.
+ *
+ * @param array|int $statuses
*
+ * @return int
+ *
+ * @throws \API_Exception
* @throws \CRM_Core_Exception
+ */
+ protected function getRowCount($statuses = []) {
+ $statuses = (array) $statuses;
+ return $this->getDataSourceObject()->getRowCount($statuses);
+ }
+
+ /**
+ * Outputs and downloads the csv of outcomes from an import job.
+ *
+ * This gets the rows from the temp table that match the relevant status
+ * and output them as a csv.
+ *
* @throws \API_Exception
+ * @throws \League\Csv\CannotInsertRecord
+ * @throws \CRM_Core_Exception
+ */
+ public static function outputCSV(): void {
+ $userJobID = CRM_Utils_Request::retrieveValue('user_job_id', 'Integer', NULL, TRUE);
+ $status = CRM_Utils_Request::retrieveValue('status', 'String', NULL, TRUE);
+ $saveFileName = CRM_Import_Parser::saveFileName($status);
+
+ $form = new CRM_Import_Forms();
+ $form->controller = new CRM_Core_Controller();
+ $form->set('user_job_id', $userJobID);
+
+ $form->getUserJob();
+ $writer = Writer::createFromFileObject(new SplTempFileObject());
+ $headers = $form->getColumnHeaders();
+ if ($headers) {
+ array_unshift($headers, ts('Reason'));
+ array_unshift($headers, ts('Line Number'));
+ $writer->insertOne($headers);
+ }
+ $writer->addFormatter(['CRM_Import_Forms', 'reorderOutput']);
+ // Note this might be more inefficient that iterating the result
+ // set & doing insertOne - possibly something to explore later.
+ $writer->insertAll($form->getDataRows($status));
+
+ CRM_Utils_System::setHttpHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
+ CRM_Utils_System::setHttpHeader('Content-Description', 'File Transfer');
+ CRM_Utils_System::setHttpHeader('Content-Type', 'text/csv; charset=UTF-8');
+ $writer->output($saveFileName);
+ CRM_Utils_System::civiExit();
+ }
+
+ /**
+ * When outputting the row as a csv, more the last 2 rows to the start.
+ *
+ * This is because the id and status message fields are at the end. It may make sense
+ * to move them to the start later, when order code cleanup has happened...
+ *
+ * @param array $record
*/
- protected function getDataRows(int $limit): array {
- return $this->getDataSourceObject()->setLimit($limit)->getRows();
+ public static function reorderOutput(array $record): array {
+ $rowNumber = array_pop($record);
+ $message = array_pop($record);
+ // Also pop off the status - but we are not going to use this at this stage.
+ array_pop($record);
+ // Related entities
+ array_pop($record);
+ // Entity_id
+ array_pop($record);
+ array_unshift($record, $message);
+ array_unshift($record, $rowNumber);
+ return $record;
+ }
+
+ /**
+ * Get the url to download the relevant csv file.
+ * @param string $status
+ *
+ * @return string
+ */
+ protected function getDownloadURL(string $status): string {
+ return CRM_Utils_System::url('civicrm/import/outcome', [
+ 'user_job_id' => $this->get('user_job_id'),
+ 'status' => $status,
+ 'reset' => 1,
+ ]);
}
/**
* @throws \API_Exception
*/
protected function getAvailableFields(): array {
- $parser = new CRM_Contact_Import_Parser_Contact();
- $parser->setUserJobID($this->getUserJobID());
- return $parser->getAvailableFields();
+ return $this->getParser()->getAvailableFields();
+ }
+
+ /**
+ * Get an instance of the parser class.
+ *
+ * @return \CRM_Contact_Import_Parser_Contact|\CRM_Contribute_Import_Parser_Contribution
+ */
+ protected function getParser() {
+ return NULL;
+ }
+
+ /**
+ * Get the mapped fields as an array of labels.
+ *
+ * e.g
+ * ['First Name', 'Employee Of - First Name', 'Home - Street Address']
+ *
+ * @return array
+ * @throws \API_Exception
+ */
+ protected function getMappedFieldLabels(): array {
+ $mapper = [];
+ $parser = $this->getParser();
+ foreach ($this->getSubmittedValue('mapper') as $columnNumber => $mappedField) {
+ $mapper[$columnNumber] = $parser->getMappedFieldLabel($parser->getMappingFieldFromMapperInput($mappedField, 0, $columnNumber));
+ }
+ return $mapper;
+ }
+
+ /**
+ * Assign variables required for the MapField form.
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
+ */
+ protected function assignMapFieldVariables(): void {
+ $this->addExpectedSmartyVariable('highlightedRelFields');
+ $this->_columnCount = $this->getNumberOfColumns();
+ $this->_columnNames = $this->getColumnHeaders();
+ $this->_dataValues = array_values($this->getDataRows([], 2));
+ $this->assign('columnNames', $this->getColumnHeaders());
+ $this->assign('highlightedFields', $this->getHighlightedFields());
+ $this->assign('columnCount', $this->_columnCount);
+ $this->assign('dataValues', $this->_dataValues);
+ }
+
+ /**
+ * Get the fields to be highlighted in the UI.
+ *
+ * The highlighted fields are those used to match
+ * to an existing entity.
+ *
+ * @return array
+ *
+ * @throws \CRM_Core_Exception
+ */
+ protected function getHighlightedFields(): array {
+ return [];
+ }
+
+ /**
+ * Get the data patterns to pattern match the incoming data.
+ *
+ * @return array
+ */
+ public function getDataPatterns(): array {
+ return $this->getParser()->getDataPatterns();
+ }
+
+ /**
+ * Get the data patterns to pattern match the incoming data.
+ *
+ * @return array
+ */
+ public function getHeaderPatterns(): array {
+ return $this->getParser()->getHeaderPatterns();
+ }
+
+ /**
+ * Has the user chosen to update existing records.
+ * @return bool
+ */
+ protected function isUpdateExisting(): bool {
+ return ((int) $this->getSubmittedValue('onDuplicate')) === CRM_Import_Parser::DUPLICATE_UPDATE;
+ }
+
+ /**
+ * Has the user chosen to update existing records.
+ * @return bool
+ */
+ protected function isSkipExisting(): bool {
+ return ((int) $this->getSubmittedValue('onDuplicate')) === CRM_Import_Parser::DUPLICATE_SKIP;
}
}