Merge pull request #22487 from mattwire/repeattransactiontemplatecontribution
[civicrm-core.git] / CRM / Import / DataSource / CSV.php
index 48687237cb9e955386dc7cd511cf86e33ee5b5e7..79e68a0d7ff21af016d3f4153316d18906047766 100644 (file)
@@ -19,21 +19,20 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
     NUM_ROWS_TO_INSERT = 100;
 
   /**
-   * Provides information about the data source.
+   * Form fields declared for this datasource.
    *
-   * @return array
-   *   collection of info about this data source
+   * @var string[]
    */
-  public function getInfo() {
-    return ['title' => ts('Comma-Separated Values (CSV)')];
-  }
+  protected $submittableFields = ['skipColumnHeader', 'uploadField'];
 
   /**
-   * Set variables up before form is built.
+   * Provides information about the data source.
    *
-   * @param CRM_Core_Form $form
+   * @return array
+   *   collection of info about this data source
    */
-  public function preProcess(&$form) {
+  public function getInfo(): array {
+    return ['title' => ts('Comma-Separated Values (CSV)')];
   }
 
   /**
@@ -49,9 +48,7 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
   public function buildQuickForm(&$form) {
     $form->add('hidden', 'hidden_dataSource', 'CRM_Import_DataSource_CSV');
 
-    $config = CRM_Core_Config::singleton();
-
-    $uploadFileSize = CRM_Utils_Number::formatUnitSize($config->maxFileSize . 'm', TRUE);
+    $uploadFileSize = CRM_Utils_Number::formatUnitSize(Civi::settings()->get('maxFileSize') . 'm', TRUE);
     //Fetch uploadFileSize from php_ini when $config->maxFileSize is set to "no limit".
     if (empty($uploadFileSize)) {
       $uploadFileSize = CRM_Utils_Number::formatUnitSize(ini_get('upload_max_filesize'), TRUE);
@@ -71,41 +68,33 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
   }
 
   /**
-   * Process the form submission.
-   *
-   * @param array $params
-   * @param string $db
-   * @param \CRM_Core_Form $form
+   * Initialize the datasource, based on the submitted values stored in the user job.
    *
+   * @throws \API_Exception
    * @throws \CRM_Core_Exception
    */
-  public function postProcess(&$params, &$db, &$form) {
-    $file = $params['uploadFile']['name'];
-    $result = self::_CsvToTable($db,
-      $file,
-      CRM_Utils_Array::value('skipColumnHeader', $params, FALSE),
-      CRM_Utils_Array::value('import_table_name', $params),
-      CRM_Utils_Array::value('fieldSeparator', $params, ',')
+  public function initialize(): void {
+    $result = self::_CsvToTable(
+      $this->getSubmittedValue('uploadFile')['name'],
+      $this->getSubmittedValue('skipColumnHeader'),
+      $this->getSubmittedValue('fieldSeparator') ?? ','
     );
+    $this->addTrackingFieldsToTable($result['import_table_name']);
 
-    $form->set('originalColHeader', CRM_Utils_Array::value('original_col_header', $result));
-
-    $table = $result['import_table_name'];
-    $importJob = new CRM_Contact_Import_ImportJob($table);
-    $form->set('importTableName', $importJob->getTableName());
+    $this->updateUserJobMetadata('DataSource', [
+      'table_name' => $result['import_table_name'],
+      'column_headers' => $this->getSubmittedValue('skipColumnHeader') ? $result['column_headers'] : [],
+      'number_of_columns' => $result['number_of_columns'],
+    ]);
   }
 
   /**
    * Create a table that matches the CSV file and populate it with the file's contents
    *
-   * @param object $db
-   *   Handle to the database connection.
    * @param string $file
    *   File name to load.
    * @param bool $headers
    *   Whether the first row contains headers.
-   * @param string $tableName
-   *   Name of table from which data imported.
    * @param string $fieldSeparator
    *   Character that separates the various columns in the file.
    *
@@ -114,10 +103,8 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
    * @throws \CRM_Core_Exception
    */
   private static function _CsvToTable(
-    &$db,
     $file,
     $headers = FALSE,
-    $tableName = NULL,
     $fieldSeparator = ','
   ) {
     $result = [];
@@ -141,7 +128,7 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
     // create the column names from the CSV header or as col_0, col_1, etc.
     if ($headers) {
       //need to get original headers.
-      $result['original_col_header'] = $firstrow;
+      $result['column_headers'] = $firstrow;
 
       $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
       $columns = array_map($strtolower, $firstrow);
@@ -186,9 +173,6 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
       }
     }
 
-    if ($tableName) {
-      CRM_Core_DAO::executeQuery("DROP TABLE IF EXISTS $tableName");
-    }
     $table = CRM_Utils_SQL_TempTable::build()->setDurable();
     $tableName = $table->getName();
     CRM_Core_DAO::executeQuery("DROP TABLE IF EXISTS $tableName");
@@ -214,6 +198,10 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
       if (count($row) != $numColumns) {
         continue;
       }
+      // A blank line will be array(0 => NULL)
+      if ($row === [NULL]) {
+        continue;
+      }
 
       if (!$first) {
         $sql .= ', ';
@@ -222,10 +210,7 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
       $first = FALSE;
 
       // CRM-17859 Trim non-breaking spaces from columns.
-      $row = array_map(
-        function($string) {
-          return trim($string, chr(0xC2) . chr(0xA0));
-        }, $row);
+      $row = array_map(['CRM_Import_DataSource_CSV', 'trimNonBreakingSpaces'], $row);
       $row = array_map(['CRM_Core_DAO', 'escapeString'], $row);
       $sql .= "('" . implode("', '", $row) . "')";
       $count++;
@@ -247,8 +232,32 @@ class CRM_Import_DataSource_CSV extends CRM_Import_DataSource {
 
     //get the import tmp table name.
     $result['import_table_name'] = $tableName;
-
+    $result['number_of_columns'] = $numColumns;
     return $result;
   }
 
+  /**
+   * Trim non-breaking spaces in a multibyte-safe way.
+   * See also dev/core#2127 - avoid breaking strings ending in à or any other
+   * unicode character sharing the same 0xA0 byte as a non-breaking space.
+   *
+   * @param string $string
+   * @return string The trimmed string
+   */
+  public static function trimNonBreakingSpaces(string $string): string {
+    $encoding = mb_detect_encoding($string, NULL, TRUE);
+    if ($encoding === FALSE) {
+      // This could mean a couple things. One is that the string is
+      // ASCII-encoded but contains a non-breaking space, which causes
+      // php to fail to detect the encoding. So let's just do what we
+      // did before which works in that situation and is at least no
+      // worse in other situations.
+      return trim($string, chr(0xC2) . chr(0xA0));
+    }
+    elseif ($encoding !== 'UTF-8') {
+      $string = mb_convert_encoding($string, 'UTF-8', [$encoding]);
+    }
+    return preg_replace("/^(\u{a0})+|(\u{a0})+$/", '', $string);
+  }
+
 }