Merge pull request #23742 from eileenmcnaughton/import_remove
[civicrm-core.git] / CRM / Import / Forms.php
index 05fa85b04075ec6da176ca0e79ff6208c1aa31d1..0b37ea76b221e8e4ffae146d797c176a319a77a0 100644 (file)
@@ -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.
    *
@@ -112,6 +118,7 @@ class CRM_Import_Forms extends CRM_Core_Form {
     '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.
@@ -125,13 +132,15 @@ class CRM_Import_Forms extends CRM_Core_Form {
    * @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)) {
@@ -291,8 +300,6 @@ class CRM_Import_Forms extends CRM_Core_Form {
    * 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();
@@ -320,7 +327,6 @@ class CRM_Import_Forms extends CRM_Core_Form {
    * all forms.
    *
    * @return string[]
-   * @throws \CRM_Core_Exception
    */
   protected function getSubmittableFields(): array {
     $dataSourceFields = array_fill_keys($this->getDataSourceFields(), 'DataSource');
@@ -367,7 +373,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',
@@ -437,15 +443,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
    */
-  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,
+    ]);
   }
 
   /**
@@ -457,9 +555,99 @@ 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
+   */
+  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;
   }
 
 }