X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FImport%2FForms.php;h=cd81aa4585f96471974edacf10b71bd26c218b80;hb=c4a6ea3f4397aeec5ec3705ac3dbf73e3845772c;hp=eadc6504773274fcd60955b4e4f50fd6a4e4218f;hpb=9aca97f3ee09316b80e9c1ca50296178d33fa949;p=civicrm-core.git diff --git a/CRM/Import/Forms.php b/CRM/Import/Forms.php index eadc650477..cd81aa4585 100644 --- a/CRM/Import/Forms.php +++ b/CRM/Import/Forms.php @@ -16,6 +16,7 @@ */ use Civi\Api4\UserJob; +use League\Csv\Writer; /** * This class helps the forms within the import flow access submitted & parsed values. @@ -62,6 +63,11 @@ class CRM_Import_Forms extends CRM_Core_Form { */ protected $userJob; + /** + * @var \CRM_Import_Parser + */ + protected $parser; + /** * Get User Job. * @@ -131,7 +137,10 @@ class CRM_Import_Forms extends CRM_Core_Form { 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)) { @@ -240,6 +249,35 @@ class CRM_Import_Forms extends CRM_Core_Form { } } + /** + * Flush datasource on re-submission of the form. + * + * If the form has been re-submitted the datasource might have changed. + * We tell the dataSource class to remove any tables (and potentially files) + * created last form submission. + * + * If the DataSource in use is unchanged (ie still CSV or still SQL) + * we also pass in the new variables. In theory it could decide that they + * have not actually changed and it doesn't need to do any cleanup. + * + * In practice the datasource classes blast away as they always have for now + * - however, the sql class, for example, might realise the fields it cares + * about are unchanged and not flush the table. + * + * @throws \API_Exception + * @throws \CRM_Core_Exception + */ + protected function flushDataSource(): void { + // If the form has been resubmitted the datasource might have changed. + // We give the datasource a chance to clean up any tables it might have + // created. If we are still using the same type of datasource (e.g still + // an sql query + $oldDataSource = $this->getUserJobSubmittedValues()['dataSource']; + $oldDataSourceObject = new $oldDataSource($this->getUserJobID()); + $newParams = $this->getSubmittedValue('dataSource') === $oldDataSource ? $this->getSubmittedValues() : []; + $oldDataSourceObject->purge($newParams); + } + /** * Get the relevant datasource object. * @@ -338,7 +376,7 @@ class CRM_Import_Forms extends CRM_Core_Form { $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', @@ -408,15 +446,107 @@ class CRM_Import_Forms extends CRM_Core_Form { * 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 + */ + 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 getDataRows(int $limit): array { - return $this->getDataSourceObject()->setLimit($limit)->getRows(); + 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, + ]); } /** @@ -428,9 +558,84 @@ class CRM_Import_Forms extends CRM_Core_Form { * @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 + * @throws \CRM_Core_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(); } }