dev/core#3663 fix output regression on csv output
authorEileen McNaughton <emcnaughton@wikimedia.org>
Fri, 17 Jun 2022 00:11:12 +0000 (12:11 +1200)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Fri, 17 Jun 2022 00:14:41 +0000 (12:14 +1200)
This fixes a very-recent rc regression whereby the file error output was
not correctly referring to the fields to be exported.

Note that in order to make it testable I had to
- remove header-setting that duplicates work done by the csv package
- remove extraneous echos in the GenCode

CRM/Core/CodeGen/Main.php
CRM/Import/DataSource.php
CRM/Import/DataSource/CSV.php
CRM/Import/DataSource/SQL.php
CRM/Import/Forms.php
CRM/Import/Parser.php
Civi/Test.php
tests/phpunit/CRM/Contact/Import/Form/data/column_names_casing.csv [new file with mode: 0644]
tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php

index d108102259617250657d3a13a654d5bb4e78d99d..fe90d38f5039deea1c6fd4067f03c5b672de676e 100644 (file)
@@ -56,6 +56,23 @@ class CRM_Core_CodeGen_Main {
    */
   public $sourceDigest;
 
+  /**
+   * Should the specification be allowed to echo output.
+   *
+   * @var bool
+   */
+  protected $verbose = TRUE;
+
+  /**
+   * @param bool $verbose
+   *
+   * @return CRM_Core_CodeGen_Main
+   */
+  public function setVerbose(bool $verbose): CRM_Core_CodeGen_Main {
+    $this->verbose = $verbose;
+    return $this;
+  }
+
   /**
    * @param $CoreDAOCodePath
    * @param $sqlCodePath
@@ -142,7 +159,7 @@ Alternatively you can get a version of CiviCRM that matches your PHP version
   public function init() {
     if (!$this->database || !$this->tables) {
       $specification = new CRM_Core_CodeGen_Specification();
-      $specification->parse($this->schemaPath, $this->buildVersion);
+      $specification->parse($this->schemaPath, $this->buildVersion, $this->verbose);
       $this->database = $specification->database;
       $this->tables = $specification->tables;
     }
index df8ea13d12664a4d455b908c5f0b0b06040814ef..078211df7a7c6c6d65f929e6cf6ffdded89c1295 100644 (file)
@@ -272,6 +272,24 @@ abstract class CRM_Import_DataSource {
     return $this->getUserJob()['metadata']['DataSource']['column_headers'];
   }
 
+  /**
+   * Get the field names of the fields holding data in the import tracking table.
+   *
+   * @return array
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   */
+  public function getDataFieldNames(): array {
+    $result = CRM_Core_DAO::executeQuery(
+      'SHOW FIELDS FROM ' . $this->getTableName() . "
+      WHERE Field NOT LIKE '\_%'");
+    $fields = [];
+    while ($result->fetch()) {
+      $fields[] = $result->Field;
+    }
+    return $fields;
+  }
+
   /**
    * Get an array of column headers, if any.
    *
@@ -572,7 +590,7 @@ abstract class CRM_Import_DataSource {
    * @return string
    */
   private function getSelectClause(): string {
-    return $this->getSelectFields() ? implode(', ', $this->getSelectFields()) : '*';
+    return $this->getSelectFields() ? '`' . implode('`, `', $this->getSelectFields()) . '`' : '*';
   }
 
   /**
index 4712ce30e91e468b925ef16d8fc333f06c484c6b..2c8a19b3c42df683e75740f813e2753d28781cdc 100644 (file)
@@ -131,6 +131,7 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
 
       $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
       $columns = array_map($strtolower, $firstrow);
+      $columns = array_map('trim', $columns);
       $columns = str_replace(' ', '_', $columns);
       $columns = preg_replace('/[^a-z_]/', '', $columns);
 
index 78b20839f9be3f4bb87f7a2799c143d1076af521..d552d45103d9dc7a66d049729ad0ee2a8550a441 100644 (file)
@@ -84,11 +84,9 @@ class CRM_Import_DataSource_SQL extends CRM_Import_DataSource {
     $tableName = $table->getName();
     $table->createWithQuery($this->getSubmittedValue('sqlQuery'));
 
-    // Get the names of the fields to be imported. Any fields starting with an
-    // underscore are considered to be internal to the import process)
+    // Get the names of the fields to be imported.
     $columnsResult = CRM_Core_DAO::executeQuery(
-      'SHOW FIELDS FROM ' . $tableName . "
-      WHERE Field NOT LIKE '\_%'");
+      'SHOW FIELDS FROM ' . $tableName);
 
     $columnNames = [];
     while ($columnsResult->fetch()) {
index b2f96352e4cfe06cf232d361c006df5eb987ae48..633bb5cb34ecf04bfab76b2f0c914a33aa59bb78 100644 (file)
@@ -468,9 +468,9 @@ class CRM_Import_Forms extends CRM_Core_Form {
    */
   protected function getOutputRows($statuses = [], int $limit = 0) {
     $statuses = (array) $statuses;
-    return $this->getDataSourceObject()->setLimit($limit)->setStatuses($statuses)
-      ->setSelectFields(array_merge(['_id', '_status_message'], $this->getColumnHeaders()))
-      ->setStatuses($statuses)->getRows();
+    $dataSource = $this->getDataSourceObject()->setLimit($limit)->setStatuses($statuses)->setStatuses($statuses);
+    $dataSource->setSelectFields(array_merge(['_id', '_status_message'], $dataSource->getDataFieldNames()));
+    return $dataSource->getRows();
   }
 
   /**
@@ -512,7 +512,7 @@ class CRM_Import_Forms extends CRM_Core_Form {
    */
   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);
+    $status = (int) CRM_Utils_Request::retrieveValue('status', 'String', NULL, TRUE);
     $saveFileName = CRM_Import_Parser::saveFileName($status);
 
     $form = new CRM_Import_Forms();
@@ -523,13 +523,9 @@ class CRM_Import_Forms extends CRM_Core_Form {
     $writer = Writer::createFromFileObject(new SplTempFileObject());
     $headers = $form->getOutputColumnsHeaders();
     $writer->insertOne($headers);
-    // Note this might be more inefficient that iterating the result
+    // Note this might be more inefficient by iterating the result
     // set & doing insertOne - possibly something to explore later.
     $writer->insertAll($form->getOutputRows($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();
   }
index 39ab0f52ebdd6299c3ba4bfff4eb9b22f0dccf06..006d202534252517a7a38bb4090fb8f7a0ce53e3 100644 (file)
@@ -795,7 +795,7 @@ abstract class CRM_Import_Parser {
   /**
    * Determines the file name based on error code.
    *
-   * @var $type error code constant
+   * @var int $type code constant
    * @return string
    */
   public static function saveFileName($type) {
index 3deca30fbc2862b12cc7a928e51e2eb83de0918b..c19a400c0436ca4cf0b1eef833d87729699da3e4 100644 (file)
@@ -166,6 +166,7 @@ class Test {
     if (!isset(self::$singletons['codeGen'])) {
       $civiRoot = str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__));
       $codeGen = new \CRM_Core_CodeGen_Main("$civiRoot/CRM/Core/DAO", "$civiRoot/sql", $civiRoot, "$civiRoot/templates", NULL, "UnitTests", NULL, "$civiRoot/xml/schema/Schema.xml", NULL);
+      $codeGen->setVerbose(FALSE);
       $codeGen->init();
       self::$singletons['codeGen'] = $codeGen;
     }
diff --git a/tests/phpunit/CRM/Contact/Import/Form/data/column_names_casing.csv b/tests/phpunit/CRM/Contact/Import/Form/data/column_names_casing.csv
new file mode 100644 (file)
index 0000000..e019c69
--- /dev/null
@@ -0,0 +1,2 @@
+FIRSTNAME,LASTNAME,ID,IND/ORG,Do Not Mail,Valid Address
+Mary,Jones,1,Individual,0,Yes
index 01150919d32de9d3d3417a665f825c3c9abdca44..f9f97dd8174f091b94e3ee238200464cb66d84d7 100644 (file)
@@ -1061,13 +1061,27 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
    *
    * @throws \API_Exception
    * @throws \CRM_Core_Exception
+   * @throws \League\Csv\CannotInsertRecord
    */
-  public function testImport($csv, $mapper, $expectedError, $expectedOutcomes = [], $submittedValues = []): void {
+  public function testImport($csv, $mapper, $expectedOutcomes = [], $submittedValues = []): void {
     $this->importCSV($csv, $mapper, $submittedValues);
     $dataSource = new CRM_Import_DataSource_CSV(UserJob::get(FALSE)->setSelect(['id'])->execute()->first()['id']);
     foreach ($expectedOutcomes as $outcome => $count) {
       $this->assertEquals($dataSource->getRowCount([$outcome]), $count);
     }
+    ob_start();
+    $_REQUEST['user_job_id'] = $dataSource->getUserJobID();
+    $_REQUEST['status'] = array_key_first($expectedOutcomes);
+    try {
+      CRM_Import_Forms::outputCSV();
+    }
+    catch (CRM_Core_Exception_PrematureExitException $e) {
+      // For now just check it got this far without error.
+      ob_end_clean();
+      return;
+    }
+    ob_end_clean();
+    $this->fail('Should have resulted in a premature exit exception');
   }
 
   /**
@@ -1093,23 +1107,25 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
    */
   public function importDataProvider(): array {
     return [
+      'column_names_casing.csv' => [
+        'csv' => 'column_names_casing.csv',
+        'mapper' => [['first_name'], ['last_name'], ['do_not_import'], ['do_not_import'], ['do_not_import'], ['do_not_import']],
+        'expected_outcomes' => [CRM_Import_Parser::VALID => 1],
+      ],
       'individual_unicode.csv' => [
         'csv' => 'individual_unicode.csv',
         'mapper' => [['first_name'], ['last_name'], ['url', 1], ['country', 1]],
-        'expected_error' => '',
         'expected_outcomes' => [CRM_Import_Parser::VALID => 1],
       ],
       'individual_invalid_sub_type' => [
         'csv' => 'individual_invalid_contact_sub_type.csv',
         'mapper' => [['first_name'], ['last_name'], ['contact_sub_type']],
-        'expected_error' => '',
         'expected_outcomes' => [CRM_Import_Parser::ERROR => 1],
       ],
       //Record duplicates multiple contacts
       'organization_multiple_duplicates_invalid' => [
         'csv' => 'organization_multiple_duplicates_invalid.csv',
         'mapper' => [['organization_name'], ['email']],
-        'expected_error' => '',
         'expected_outcomes' => [
           CRM_Import_Parser::VALID => 2,
           CRM_Import_Parser::ERROR => 1,
@@ -1122,7 +1138,6 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
       'individual_invalid_external_identifier_email_mismatch' => [
         'csv' => 'individual_invalid_external_identifier_email_mismatch.csv',
         'mapper' => [['first_name'], ['last_name'], ['email'], ['external_identifier']],
-        'expected_error' => '',
         'expected_outcomes' => [
           CRM_Import_Parser::VALID => 2,
           CRM_Import_Parser::ERROR => 1,