Fixes for Activity import
[civicrm-core.git] / CRM / Activity / Import / Parser / Activity.php
index d90ae708a280cc3a6046f60ffc23a0c859602e10..cd186dbfb3186d8893e10824a6e83a0352974543 100644 (file)
@@ -30,33 +30,12 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
    */
   protected $_newActivity;
 
-  protected $_fileName;
-
-  /**
-   * Imported file size.
-   * @var int
-   */
-  protected $_fileSize;
-
-  /**
-   * Separator being used.
-   * @var string
-   */
-  protected $_separator;
-
   /**
    * Total number of lines in file.
    * @var int
    */
   protected $_lineCount;
 
-  /**
-   * Whether the file has a column header or not.
-   *
-   * @var bool
-   */
-  protected $_haveColumnHeader;
-
   /**
    * Class constructor.
    *
@@ -71,24 +50,9 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
    * The initializer code, called before the processing.
    */
   public function init() {
-    $activityContact = CRM_Activity_BAO_ActivityContact::import();
-    $activityTarget['target_contact_id'] = $activityContact['contact_id'];
-    $fields = array_merge(CRM_Activity_BAO_Activity::importableFields(),
-      $activityTarget
-    );
-
-    $fields = array_merge($fields, [
-      'source_contact_id' => [
-        'title' => ts('Source Contact'),
-        'headerPattern' => '/Source.Contact?/i',
-      ],
-      'activity_label' => [
-        'title' => ts('Activity Type Label'),
-        'headerPattern' => '/(activity.)?type label?/i',
-      ],
-    ]);
-
-    foreach ($fields as $name => $field) {
+    $this->setFieldMetadata();
+
+    foreach ($this->importableFieldsMetadata as $name => $field) {
       $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT);
       $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//');
       $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//');
@@ -129,41 +93,12 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
    *
    * @param array $values
    *   The array of values belonging to this line.
-   *
-   * @return int
-   *   CRM_Import_Parser::VALID for success or
-   *   CRM_Import_Parser::ERROR for error.
-   *
-   * @throws \CRM_Core_Exception
    */
-  public function import(&$values) {
+  public function import($values) {
+    $rowNumber = (int) ($values[array_key_last($values)]);
     // First make sure this is a valid line
     try {
-      $this->validateValues($values);
-
-      $params = $this->getApiReadyParams($values);
-      // For date-Formats.
-      $session = CRM_Core_Session::singleton();
-      $dateType = $session->get('dateTypes');
-
-      $customFields = CRM_Core_BAO_CustomField::getFields('Activity');
-
-      foreach ($params as $key => $val) {
-        if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
-          if (!empty($customFields[$customFieldID]) && $customFields[$customFieldID]['data_type'] == 'Date') {
-            $this->formatCustomDate($params, $params, $dateType, $key);
-          }
-          elseif (!empty($customFields[$customFieldID]) && $customFields[$customFieldID]['data_type'] == 'Boolean') {
-            $params[$key] = CRM_Utils_String::strtoboolstr($val);
-          }
-        }
-        elseif ($key === 'activity_date_time') {
-          $params[$key] = CRM_Utils_Date::formatDate($val, $dateType);
-        }
-        elseif ($key === 'activity_subject') {
-          $params['subject'] = $val;
-        }
-      }
+      $params = $this->getMappedRow($values);
 
       if (empty($params['external_identifier']) && empty($params['target_contact_id'])) {
 
@@ -175,20 +110,19 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
 
         if (!empty($matchedIDs)) {
           if (count($matchedIDs) > 1) {
-            array_unshift($values, 'Multiple matching contact records detected for this row. The activity was not imported');
-            return CRM_Import_Parser::ERROR;
+            throw new CRM_Core_Exception('Multiple matching contact records detected for this row. The activity was not imported');
           }
           $cid = $matchedIDs[0];
           $params['target_contact_id'] = $cid;
           $params['version'] = 3;
           $newActivity = civicrm_api('activity', 'create', $params);
           if (!empty($newActivity['is_error'])) {
-            array_unshift($values, $newActivity['error_message']);
-            return CRM_Import_Parser::ERROR;
+            throw new CRM_Core_Exception($newActivity['error_message']);
           }
 
           $this->_newActivity[] = $newActivity['id'];
-          return CRM_Import_Parser::VALID;
+          $this->setImportStatus($rowNumber, 'IMPORTED', '', $newActivity['id']);
+          return;
 
         }
         // Using new Dedupe rule.
@@ -220,8 +154,7 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
           }
         }
 
-        array_unshift($values, 'No matching Contact found for (' . $disp . ')');
-        return CRM_Import_Parser::ERROR;
+        throw new CRM_Core_Exception('No matching Contact found for (' . $disp . ')');
       }
       if (!empty($params['external_identifier'])) {
         $targetContactId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
@@ -231,30 +164,53 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
         if (!empty($params['target_contact_id']) &&
           $params['target_contact_id'] != $targetContactId
         ) {
-          array_unshift($values, 'Mismatch of External ID:' . $params['external_identifier'] . ' and Contact Id:' . $params['target_contact_id']);
-          return CRM_Import_Parser::ERROR;
+          throw new CRM_Core_Exception('Mismatch of External ID:' . $params['external_identifier'] . ' and Contact Id:' . $params['target_contact_id']);
         }
         if ($targetContactId) {
           $params['target_contact_id'] = $targetContactId;
         }
         else {
-          array_unshift($values, 'No Matching Contact for External ID:' . $params['external_identifier']);
-          return CRM_Import_Parser::ERROR;
+          throw new CRM_Core_Exception('No Matching Contact for External ID:' . $params['external_identifier']);
         }
       }
 
       $params['version'] = 3;
       $newActivity = civicrm_api('activity', 'create', $params);
       if (!empty($newActivity['is_error'])) {
-        array_unshift($values, $newActivity['error_message']);
-        return CRM_Import_Parser::ERROR;
+        throw new CRM_Core_Exception($newActivity['error_message']);
       }
     }
     catch (CRM_Core_Exception $e) {
-      return $this->addError($values, [$e->getMessage()]);
+      $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage());
+      return;
     }
     $this->_newActivity[] = $newActivity['id'];
-    return CRM_Import_Parser::VALID;
+    $this->setImportStatus($rowNumber, 'IMPORTED', '', $newActivity['id']);
+  }
+
+  /**
+   * Get the row from the csv mapped to our parameters.
+   *
+   * @param array $values
+   *
+   * @return array
+   * @throws \API_Exception
+   */
+  public function getMappedRow(array $values): array {
+    $params = [];
+    foreach ($this->getFieldMappings() as $i => $mappedField) {
+      if ($mappedField['name'] === 'do_not_import') {
+        continue;
+      }
+      if ($mappedField['name']) {
+        $fieldName = $this->getFieldMetadata($mappedField['name'])['name'];
+        if (in_array($mappedField['name'], ['target_contact_id', 'source_contact_id'])) {
+          $fieldName = $mappedField['name'];
+        }
+        $params[$fieldName] = $this->getTransformedFieldValue($mappedField['name'], $values[$i]);
+      }
+    }
+    return $params;
   }
 
   /**
@@ -273,6 +229,13 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
     return $row[$this->getFieldIndex($fieldName)] ?? NULL;
   }
 
+  /**
+   * @return array
+   */
+  protected function getRequiredFields(): array {
+    return [['activity_type_id' => ts('Activity Type'), 'activity_date_time' => ts('Activity Date')]];
+  }
+
   /**
    * Get the index for the given field.
    *
@@ -362,41 +325,6 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
     }
   }
 
-  /**
-   * @param array $values
-   *
-   * @throws \CRM_Core_Exception
-   */
-  public function validateValues(array $values): void {
-    // Check required fields if this is not an update.
-    if (!$this->getFieldValue($values, 'activity_id')) {
-      if (!$this->getFieldValue($values, 'activity_label')
-        && !$this->getFieldValue($values, 'activity_type_id')) {
-        throw new CRM_Core_Exception(ts('Missing required fields: Activity type label or Activity type ID'));
-      }
-      if (!$this->getFieldValue($values, 'activity_date_time')) {
-        throw new CRM_Core_Exception(ts('Missing required fields'));
-      }
-    }
-
-    $this->validateActivityTypeIDAndLabel($values);
-    if ($this->getFieldValue($values, 'activity_date_time')
-      && !$this->isValidDate($this->getFieldValue($values, 'activity_date_time'))) {
-      throw new CRM_Core_Exception(ts('Invalid Activity Date'));
-    }
-
-    if ($this->getFieldValue($values, 'activity_engagement_level')
-      && !CRM_Utils_Rule::positiveInteger($this->getFieldValue($values, 'activity_engagement_level'))) {
-      throw new CRM_Core_Exception(ts('Activity Engagement Index'));
-    }
-
-    $targetContactID = $this->getFieldValue($values, 'target_contact_id');
-    if ($targetContactID && !$this->isValidContactID($targetContactID)) {
-      throw new CRM_Core_Exception("Invalid Contact ID: There is no contact record with contact_id = " . CRM_Utils_Type::escape($targetContactID, 'String'));
-    }
-    $this->validateCustomFields($values);
-  }
-
   /**
    * Get array of parameters formatted for the api from the submitted values.
    *
@@ -426,9 +354,6 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
    * @param int $onDuplicate
    * @param int $statusID
    * @param int $totalRowCount
-   *
-   * @return mixed
-   * @throws Exception
    */
   public function run(
     array $fileName,
@@ -440,29 +365,14 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
           $statusID = NULL,
           $totalRowCount = NULL
   ) {
-
-    $fileName = $fileName['name'];
-
     $this->init();
 
-    $this->_haveColumnHeader = $skipColumnHeader;
-
-    $this->_separator = $separator;
-
-    $fd = fopen($fileName, "r");
-    if (!$fd) {
-      return FALSE;
-    }
-
     $this->_lineCount = 0;
     $this->_invalidRowCount = $this->_validCount = 0;
     $this->_totalCount = 0;
 
     $this->_errors = [];
     $this->_warnings = [];
-
-    $this->_fileSize = number_format(filesize($fileName) / 1024.0, 2);
-
     if ($mode == self::MODE_MAPFIELD) {
       $this->_rows = [];
     }
@@ -474,31 +384,11 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
       $startTimestamp = $currTimestamp = $prevTimestamp = time();
     }
 
-    while (!feof($fd)) {
+    $dataSource = $this->getDataSourceObject();
+    $dataSource->setStatuses(['new']);
+    while ($row = $dataSource->getRow()) {
       $this->_lineCount++;
-
-      $values = fgetcsv($fd, 8192, $separator);
-      if (!$values) {
-        continue;
-      }
-
-      self::encloseScrub($values);
-
-      // skip column header if we're not in mapfield mode
-      if ($mode != self::MODE_MAPFIELD && $skipColumnHeader) {
-        $skipColumnHeader = FALSE;
-        continue;
-      }
-
-      // Trim whitespace around the values.
-      foreach ($values as $k => $v) {
-        $values[$k] = trim($v, " \t\r\n");
-      }
-
-      if (CRM_Utils_System::isNull($values)) {
-        continue;
-      }
-
+      $values = array_values($row);
       $this->_totalCount++;
 
       if ($mode == self::MODE_MAPFIELD) {
@@ -509,60 +399,11 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
         $returnCode = $this->summary($values);
       }
       elseif ($mode == self::MODE_IMPORT) {
-        $returnCode = $this->import($values);
+        $this->import($values);
         if ($statusID && (($this->_lineCount % 50) == 0)) {
           $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount);
         }
       }
-      else {
-        $returnCode = self::ERROR;
-      }
-
-      // note that a line could be valid but still produce a warning
-      if ($returnCode & self::VALID) {
-        $this->_validCount++;
-        if ($mode == self::MODE_MAPFIELD) {
-          $this->_rows[] = $values;
-          $this->_activeFieldCount = max($this->_activeFieldCount, count($values));
-        }
-      }
-
-      if ($returnCode & self::ERROR) {
-        $this->_invalidRowCount++;
-        $recordNumber = $this->_lineCount;
-        if ($this->_haveColumnHeader) {
-          $recordNumber--;
-        }
-        array_unshift($values, $recordNumber);
-        $this->_errors[] = $values;
-      }
-
-      // if we are done processing the maxNumber of lines, break
-      if ($this->_maxLinesToProcess > 0 && $this->_validCount >= $this->_maxLinesToProcess) {
-        break;
-      }
-    }
-
-    fclose($fd);
-
-    if ($mode == self::MODE_PREVIEW || $mode == self::MODE_IMPORT) {
-      $customHeaders = $mapper;
-
-      $customfields = CRM_Core_BAO_CustomField::getFields('Activity');
-      foreach ($customHeaders as $key => $value) {
-        if ($id = CRM_Core_BAO_CustomField::getKeyID($value)) {
-          $customHeaders[$key] = $customfields[$id][0];
-        }
-      }
-      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);
-      }
     }
   }
 
@@ -611,28 +452,7 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
    *
    * @param CRM_Core_Session $store
    */
-  public function set($store) {
-    $store->set('fileSize', $this->_fileSize);
-    $store->set('lineCount', $this->_lineCount);
-    $store->set('separator', $this->_separator);
-    $store->set('fields', $this->getSelectValues());
-
-    $store->set('headerPatterns', $this->getHeaderPatterns());
-    $store->set('dataPatterns', $this->getDataPatterns());
-    $store->set('columnCount', $this->_activeFieldCount);
-
-    $store->set('totalRowCount', $this->_totalCount);
-    $store->set('validRowCount', $this->_validCount);
-    $store->set('invalidRowCount', $this->_invalidRowCount);
-
-    if ($this->_invalidRowCount) {
-      $store->set('errorsFileName', $this->_errorFileName);
-    }
-
-    if (isset($this->_rows) && !empty($this->_rows)) {
-      $store->set('dataValues', $this->_rows);
-    }
-  }
+  public function set($store) {}
 
   /**
    * Export data to a CSV file.
@@ -661,4 +481,25 @@ class CRM_Activity_Import_Parser_Activity extends CRM_Import_Parser {
     fclose($fd);
   }
 
+  /**
+   * Ensure metadata is loaded.
+   */
+  protected function setFieldMetadata(): void {
+    if (empty($this->importableFieldsMetadata)) {
+      $activityContact = CRM_Activity_BAO_ActivityContact::import();
+      $activityTarget['target_contact_id'] = $activityContact['contact_id'];
+      $fields = array_merge(CRM_Activity_BAO_Activity::importableFields(),
+        $activityTarget
+      );
+
+      $fields = array_merge($fields, [
+        'source_contact_id' => [
+          'title' => ts('Source Contact'),
+          'headerPattern' => '/Source.Contact?/i',
+        ],
+      ]);
+      $this->importableFieldsMetadata = $fields;
+    }
+  }
+
 }