From 99e3c5f721637dc5c43a880ae4e0f5d0f764cc53 Mon Sep 17 00:00:00 2001
From: Eileen McNaughton
Date: Sun, 24 Apr 2022 14:41:05 +1200
Subject: [PATCH] Add output form for csv-on-the-fly
---
CRM/Contact/Import/Form/MapField.php | 2 +-
CRM/Contact/Import/Form/Preview.php | 25 ++---
CRM/Contact/Import/Form/Summary.php | 9 +-
CRM/Contact/Import/Parser/Contact.php | 25 -----
CRM/Core/xml/Menu/Import.xml | 6 ++
CRM/Import/DataSource.php | 14 +--
CRM/Import/Form/Summary.php | 2 +-
CRM/Import/Forms.php | 97 ++++++++++++++++++-
templates/CRM/Contact/Import/Form/Preview.tpl | 4 +-
templates/CRM/Contact/Import/Form/Summary.tpl | 4 +-
10 files changed, 128 insertions(+), 60 deletions(-)
diff --git a/CRM/Contact/Import/Form/MapField.php b/CRM/Contact/Import/Form/MapField.php
index 1685116c53..6de8494b25 100644
--- a/CRM/Contact/Import/Form/MapField.php
+++ b/CRM/Contact/Import/Form/MapField.php
@@ -113,7 +113,7 @@ class CRM_Contact_Import_Form_MapField extends CRM_Import_Form_MapField {
$this->assign('columnNames', $this->getColumnHeaders());
//$this->_columnCount = $this->get( 'columnCount' );
$this->assign('columnCount', $this->_columnCount);
- $this->_dataValues = array_values($this->getDataRows(2));
+ $this->_dataValues = array_values($this->getDataRows([], 2));
$this->assign('dataValues', $this->_dataValues);
}
diff --git a/CRM/Contact/Import/Form/Preview.php b/CRM/Contact/Import/Form/Preview.php
index 5699d7db05..3818a1ce00 100644
--- a/CRM/Contact/Import/Form/Preview.php
+++ b/CRM/Contact/Import/Form/Preview.php
@@ -29,10 +29,11 @@ class CRM_Contact_Import_Form_Preview extends CRM_Import_Form_Preview {
/**
* Set variables up before form is built.
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
*/
public function preProcess() {
- $mapper = $this->get('mapper');
- $invalidRowCount = $this->get('invalidRowCount');
$conflictRowCount = $this->get('conflictRowCount');
$mismatchCount = $this->get('unMatchCount');
$columnNames = $this->get('columnNames');
@@ -60,11 +61,12 @@ class CRM_Contact_Import_Form_Preview extends CRM_Import_Form_Preview {
$this->set('tag', $tag);
}
- if ($invalidRowCount) {
- $urlParams = 'type=' . CRM_Import_Parser::ERROR . '&parser=CRM_Contact_Import_Parser_Contact';
- $this->set('downloadErrorRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams));
- }
+ $this->assign('downloadErrorRecordsUrl', $this->getDownloadURL(CRM_Import_Parser::ERROR));
+ $this->assign('invalidRowCount', $this->getRowCount(CRM_Import_Parser::ERROR));
+ $this->assign('validRowCount', $this->getRowCount(CRM_Import_Parser::VALID));
+ $this->assign('totalRowCount', $this->getRowCount([]));
+ // @todo conflict rows are still being output in the parser & not updating the temp table - fix
if ($conflictRowCount) {
$urlParams = 'type=' . CRM_Import_Parser::CONFLICT . '&parser=CRM_Contact_Import_Parser_Contact';
$this->set('downloadConflictRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams));
@@ -76,12 +78,7 @@ class CRM_Contact_Import_Form_Preview extends CRM_Import_Form_Preview {
}
$properties = array(
- 'columnCount',
- 'totalRowCount',
- 'validRowCount',
- 'invalidRowCount',
'conflictRowCount',
- 'downloadErrorRecordsUrl',
'downloadConflictRecordsUrl',
'downloadMismatchRecordsUrl',
);
@@ -91,7 +88,7 @@ class CRM_Contact_Import_Form_Preview extends CRM_Import_Form_Preview {
foreach ($properties as $property) {
$this->assign($property, $this->get($property));
}
- $this->assign('dataValues', $this->getDataRows(2));
+ $this->assign('dataValues', $this->getDataRows([], 2));
$this->setStatusUrl();
}
@@ -263,9 +260,7 @@ class CRM_Contact_Import_Form_Preview extends CRM_Import_Form_Preview {
$this->set('errorFile', $errorFile);
- $urlParams = 'type=' . CRM_Import_Parser::ERROR . '&parser=CRM_Contact_Import_Parser_Contact';
- $this->set('downloadErrorRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams));
-
+ // @todo - these should use the new url but are not reliably updating the table yet.
$urlParams = 'type=' . CRM_Import_Parser::CONFLICT . '&parser=CRM_Contact_Import_Parser_Contact';
$this->set('downloadConflictRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams));
diff --git a/CRM/Contact/Import/Form/Summary.php b/CRM/Contact/Import/Form/Summary.php
index 4b11b5b1fb..ab526d4e01 100644
--- a/CRM/Contact/Import/Form/Summary.php
+++ b/CRM/Contact/Import/Form/Summary.php
@@ -27,7 +27,7 @@ class CRM_Contact_Import_Form_Summary extends CRM_Import_Form_Summary {
// set the error message path to display
$this->assign('errorFile', $this->get('errorFile'));
- $totalRowCount = $this->get('totalRowCount');
+ $totalRowCount = $this->getRowCount();
$relatedCount = $this->get('relatedCount');
$totalRowCount += $relatedCount;
@@ -81,9 +81,6 @@ class CRM_Contact_Import_Form_Summary extends CRM_Import_Form_Summary {
$this->assign('dupeActionString', $dupeActionString);
$properties = [
- 'totalRowCount',
- 'validRowCount',
- 'invalidRowCount',
'conflictRowCount',
'downloadConflictRecordsUrl',
'downloadErrorRecordsUrl',
@@ -98,6 +95,10 @@ class CRM_Contact_Import_Form_Summary extends CRM_Import_Form_Summary {
foreach ($properties as $property) {
$this->assign($property, $this->get($property));
}
+ $this->assign('totalRowCount', $this->getRowCount());
+ $this->assign('validRowCount', $this->getRowCount(CRM_Import_Parser::VALID));
+ $this->assign('invalidRowCount', $this->getRowCount(CRM_Import_Parser::ERROR));
+ $this->assign('downloadDuplicateRecordsUrl', $this->getDownloadURL(CRM_Import_Parser::DUPLICATE));
$session = CRM_Core_Session::singleton();
$session->pushUserContext(CRM_Utils_System::url('civicrm/import/contact', 'reset=1'));
diff --git a/CRM/Contact/Import/Parser/Contact.php b/CRM/Contact/Import/Parser/Contact.php
index 53afad761a..4496bfc346 100644
--- a/CRM/Contact/Import/Parser/Contact.php
+++ b/CRM/Contact/Import/Parser/Contact.php
@@ -2715,15 +2715,6 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
}
}
- 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->_conflictCount) {
$headers = array_merge([
ts('Line Number'),
@@ -2732,15 +2723,6 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
$this->_conflictFileName = self::errorFileName(self::CONFLICT);
self::exportCSV($this->_conflictFileName, $headers, $this->_conflicts);
}
- if ($this->_duplicateCount) {
- $headers = array_merge([
- ts('Line Number'),
- ts('View Contact URL'),
- ], $customHeaders);
-
- $this->_duplicateFileName = self::errorFileName(self::DUPLICATE);
- self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates);
- }
if ($this->_unMatchCount) {
$headers = array_merge([
ts('Line Number'),
@@ -3064,9 +3046,6 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
$store->set('contactType', CRM_Import_Parser::CONTACT_ORGANIZATION);
}
- if ($this->_invalidRowCount) {
- $store->set('errorsFileName', $this->_errorFileName);
- }
if ($this->_conflictCount) {
$store->set('conflictsFileName', $this->_conflictFileName);
}
@@ -3084,11 +3063,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser {
if ($this->_duplicateCount) {
$store->set('duplicatesFileName', $this->_duplicateFileName);
}
- if ($this->_unparsedAddressCount) {
- $store->set('errorsFileName', $this->_errorFileName);
- }
}
- //echo "$this->_totalCount,$this->_invalidRowCount,$this->_conflictCount,$this->_duplicateCount";
}
/**
diff --git a/CRM/Core/xml/Menu/Import.xml b/CRM/Core/xml/Menu/Import.xml
index e1dec69163..33d935c634 100644
--- a/CRM/Core/xml/Menu/Import.xml
+++ b/CRM/Core/xml/Menu/Import.xml
@@ -17,6 +17,12 @@
CRM_Contact_Import_Controller
410
+ -
+ civicrm/import/outcome
+ Import results
+ access CiviCRM
+ CRM_Import_Forms::outputCSV
+
-
civicrm/import/activity
Import Activities
diff --git a/CRM/Import/DataSource.php b/CRM/Import/DataSource.php
index 77db320a37..311711e37d 100644
--- a/CRM/Import/DataSource.php
+++ b/CRM/Import/DataSource.php
@@ -454,13 +454,13 @@ abstract class CRM_Import_DataSource {
/**
* Get the mapping of constants to database status codes.
*
- * @return string[]
+ * @return array[]
*/
- protected function getStatusMapping() {
+ protected function getStatusMapping(): array {
return [
- CRM_Import_Parser::VALID => 'imported',
- CRM_Import_Parser::ERROR => 'error',
- CRM_Import_Parser::DUPLICATE => 'duplicate',
+ CRM_Import_Parser::VALID => ['imported', 'new'],
+ CRM_Import_Parser::ERROR => ['error'],
+ CRM_Import_Parser::DUPLICATE => ['duplicate'],
];
}
@@ -473,7 +473,9 @@ abstract class CRM_Import_DataSource {
if (!empty($this->statuses)) {
$statuses = [];
foreach ($this->statuses as $status) {
- $statuses[] = '"' . $this->getStatusMapping()[$status] . '"';
+ foreach ($this->getStatusMapping()[$status] as $statusName) {
+ $statuses[] = '"' . $statusName . '"';
+ }
}
return ' WHERE _status IN (' . implode(',', $statuses) . ')';
}
diff --git a/CRM/Import/Form/Summary.php b/CRM/Import/Form/Summary.php
index e12cbb4fa1..b27af617ee 100644
--- a/CRM/Import/Form/Summary.php
+++ b/CRM/Import/Form/Summary.php
@@ -21,7 +21,7 @@
* TODO: CRM-11254 - if preProcess and postProcess functions can be reconciled between the 5 child classes,
* those classes can be removed entirely and this class will not need to be abstract
*/
-abstract class CRM_Import_Form_Summary extends CRM_Core_Form {
+abstract class CRM_Import_Form_Summary extends CRM_Import_Forms {
/**
* Build the form object.
diff --git a/CRM/Import/Forms.php b/CRM/Import/Forms.php
index 05fa85b040..d2b7d98ad8 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.
@@ -437,15 +438,103 @@ 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::CONFLICT]
*
- * @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);
+ 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,
+ ]);
}
/**
diff --git a/templates/CRM/Contact/Import/Form/Preview.tpl b/templates/CRM/Contact/Import/Form/Preview.tpl
index e899d3daf4..5e883e2664 100644
--- a/templates/CRM/Contact/Import/Form/Preview.tpl
+++ b/templates/CRM/Contact/Import/Form/Preview.tpl
@@ -20,7 +20,7 @@
{if $invalidRowCount}
- {ts 1=$invalidRowCount 2=$downloadErrorRecordsUrl}CiviCRM has detected invalid data or formatting errors in %1 records. If you continue, these records will be skipped. OR, you can download a file with just these problem records - Download Errors. Then correct them in the original import file, cancel this import and begin again at step 1.{/ts}
+ {ts 1=$invalidRowCount 2=$downloadErrorRecordsUrl|smarty:nodefaults}CiviCRM has detected invalid data or formatting errors in %1 records. If you continue, these records will be skipped. OR, you can download a file with just these problem records - Download Errors. Then correct them in the original import file, cancel this import and begin again at step 1.{/ts}
{/if}
@@ -48,7 +48,7 @@
{$invalidRowCount} |
{ts}Rows with invalid data in one or more fields (for example, invalid email address formatting). These rows will be skipped (not imported).{/ts}
{if $invalidRowCount}
-
+
{/if}
|
diff --git a/templates/CRM/Contact/Import/Form/Summary.tpl b/templates/CRM/Contact/Import/Form/Summary.tpl
index 0b3577e65c..1a4a9baad8 100644
--- a/templates/CRM/Contact/Import/Form/Summary.tpl
+++ b/templates/CRM/Contact/Import/Form/Summary.tpl
@@ -33,7 +33,7 @@
{ts count=$invalidRowCount plural='CiviCRM has detected invalid data and/or formatting errors in %count records. These records have not been imported.'}CiviCRM has detected invalid data and/or formatting errors in one record. This record has not been imported.{/ts}
- {ts 1=$downloadErrorRecordsUrl}You can Download Errors. You may then correct them, and import the new file with the corrected data.{/ts}
+ {ts 1=$downloadErrorRecordsUrl|smarty:nodefaults}You can Download Errors. You may then correct them, and import the new file with the corrected data.{/ts}
{/if}
@@ -75,7 +75,7 @@
{$invalidRowCount} |
{ts}Rows with invalid data in one or more fields (for example, invalid email address formatting). These rows will be skipped (not imported).{/ts}
{if $invalidRowCount}
-
+
{/if}
|
--
2.25.1