Merge pull request #15810 from eileenmcnaughton/mem_fix
[civicrm-core.git] / CRM / Import / ImportProcessor.php
index ffd38c531795fa8d2287bfe189008a2505466398..79ddc3b640e357b5a8974f71f6a861ca61792a3c 100644 (file)
@@ -19,6 +19,18 @@ class CRM_Import_ImportProcessor {
    */
   protected $mappingFields = [];
 
+  /**
+   * @var array
+   */
+  protected $metadata = [];
+
+  /**
+   * Metadata keyed by field title.
+   *
+   * @var array
+   */
+  protected $metadataByTitle = [];
+
   /**
    * Get contact type being imported.
    *
@@ -27,6 +39,139 @@ class CRM_Import_ImportProcessor {
   protected $contactType;
 
   /**
+   * Get contact sub type being imported.
+   *
+   * @var string
+   */
+  protected $contactSubType;
+
+  /**
+   * Array of valid relationships for the contact type & subtype.
+   *
+   * @var array
+   */
+  protected $validRelationships = [];
+
+  /**
+   * Name of  the form.
+   *
+   * Used for js for quick form.
+   *
+   * @var string
+   */
+  protected $formName;
+
+  /**
+   * @return string
+   */
+  public function getFormName(): string {
+    return $this->formName;
+  }
+
+  /**
+   * @param string $formName
+   */
+  public function setFormName(string $formName) {
+    $this->formName = $formName;
+  }
+
+  /**
+   * @return array
+   */
+  public function getValidRelationships(): array {
+    if (!isset($this->validRelationships[$this->getContactType() . '_' . $this->getContactSubType()])) {
+      //Relationship importables
+      $relations = CRM_Contact_BAO_Relationship::getContactRelationshipType(
+        NULL, NULL, NULL, $this->getContactType(),
+        FALSE, 'label', TRUE, $this->getContactSubType()
+      );
+      asort($relations);
+      $this->setValidRelationships($relations);
+    }
+    return $this->validRelationships[$this->getContactType() . '_' . $this->getContactSubType()];
+  }
+
+  /**
+   * @param array $validRelationships
+   */
+  public function setValidRelationships(array $validRelationships) {
+    $this->validRelationships[$this->getContactType() . '_' . $this->getContactSubType()] = $validRelationships;
+  }
+
+  /**
+   * Get contact subtype for import.
+   *
+   * @return string
+   */
+  public function getContactSubType(): string {
+    return $this->contactSubType ?? '';
+  }
+
+  /**
+   * Set contact subtype for import.
+   *
+   * @param string $contactSubType
+   */
+  public function setContactSubType($contactSubType) {
+    $this->contactSubType = (string) $contactSubType;
+  }
+
+  /**
+   * Saved Mapping ID.
+   *
+   * @var int
+   */
+  protected $mappingID;
+
+  /**
+   * @return array
+   */
+  public function getMetadata(): array {
+    return $this->metadata;
+  }
+
+  /**
+   * Setting for metadata.
+   *
+   * We wrangle the label for custom fields to include the label since the
+   * metadata  trait presents it in a more 'pure' form but the label is  appended for importing.
+   *
+   * @param array $metadata
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function setMetadata(array $metadata) {
+    $fieldDetails = civicrm_api3('CustomField', 'get', [
+      'return' => ['custom_group_id.title'],
+      'options' => ['limit' => 0],
+    ])['values'];
+    foreach ($metadata as $index => $field) {
+      if (!empty($field['custom_field_id'])) {
+        // The 'label' format for import is custom group title :: custom name title
+        $metadata[$index]['name'] = $index;
+        $metadata[$index]['title'] .= ' :: ' . $fieldDetails[$field['custom_field_id']]['custom_group_id.title'];
+      }
+    }
+    $this->metadata = $metadata;
+  }
+
+  /**
+   * @return int
+   */
+  public function getMappingID(): int {
+    return $this->mappingID;
+  }
+
+  /**
+   * @param int $mappingID
+   */
+  public function setMappingID(int $mappingID) {
+    $this->mappingID = $mappingID;
+  }
+
+  /**
+   * Get the contact type for the import.
+   *
    * @return string
    */
   public function getContactType(): string {
@@ -41,30 +186,179 @@ class CRM_Import_ImportProcessor {
   }
 
   /**
+   * Set the contact type  according to the constant.
+   *
+   * @param int $contactTypeKey
+   */
+  public function setContactTypeByConstant($contactTypeKey) {
+    $constantTypeMap = [
+      CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual',
+      CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household',
+      CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization',
+    ];
+    $this->contactType = $constantTypeMap[$contactTypeKey];
+  }
+
+  /**
+   * Get Mapping Fields.
+   *
    * @return array
+   *
+   * @throws \CiviCRM_API3_Exception
    */
   public function getMappingFields(): array {
+    if (empty($this->mappingFields) && !empty($this->getMappingID())) {
+      $this->loadSavedMapping();
+    }
     return $this->mappingFields;
   }
 
   /**
+   * Set mapping fields.
+   *
+   * We do a little cleanup here too.
+   *
+   * We ensure that column numbers are set and that the fields are ordered by them.
+   *
+   * This would mean the fields could be loaded unsorted.
+   *
    * @param array $mappingFields
    */
   public function setMappingFields(array $mappingFields) {
-    $this->mappingFields = CRM_Utils_Array::rekey($mappingFields, 'column_number');
-    ksort($this->mappingFields);
-    $this->mappingFields = array_values($this->mappingFields);
+    $i = 0;
+    foreach ($mappingFields as &$mappingField) {
+      if (!isset($mappingField['column_number'])) {
+        $mappingField['column_number'] = $i;
+      }
+      if ($mappingField['column_number'] > $i) {
+        $i = $mappingField['column_number'];
+      }
+      $i++;
+    }
+    $this->mappingFields = $this->rekeyBySortedColumnNumbers($mappingFields);
   }
 
   /**
    * Get the names of the mapped fields.
+   *
+   * @throws \CiviCRM_API3_Exception
    */
   public function getFieldNames() {
     return CRM_Utils_Array::collect('name', $this->getMappingFields());
   }
 
+  /**
+   * Get the field name for the given column.
+   *
+   * @param int $columnNumber
+   *
+   * @return string
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getFieldName($columnNumber) {
+    return $this->getFieldNames()[$columnNumber];
+  }
+
+  /**
+   * Get the field name for the given column.
+   *
+   * @param int $columnNumber
+   *
+   * @return string
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getRelationshipKey($columnNumber) {
+    $field = $this->getMappingFields()[$columnNumber];
+    return empty($field['relationship_type_id']) ? NULL : $field['relationship_type_id'] . '_' . $field['relationship_direction'];
+  }
+
+  /**
+   * Get relationship key only if it is valid.
+   *
+   * @param int $columnNumber
+   *
+   * @return string|null
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getValidRelationshipKey($columnNumber) {
+    $key = $this->getRelationshipKey($columnNumber);
+    return $this->isValidRelationshipKey($key) ? $key : NULL;
+  }
+
+  /**
+   * Get the IM Provider ID.
+   *
+   * @param int $columnNumber
+   *
+   * @return int
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getIMProviderID($columnNumber) {
+    return $this->getMappingFields()[$columnNumber]['im_provider_id'] ?? NULL;
+  }
+
+  /**
+   * Get the Phone Type
+   *
+   * @param int $columnNumber
+   *
+   * @return int
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getPhoneTypeID($columnNumber) {
+    return $this->getMappingFields()[$columnNumber]['phone_type_id'] ?? NULL;
+  }
+
+  /**
+   * Get the Website Type
+   *
+   * @param int $columnNumber
+   *
+   * @return int
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getWebsiteTypeID($columnNumber) {
+    return $this->getMappingFields()[$columnNumber]['website_type_id'] ?? NULL;
+  }
+
+  /**
+   * Get the Location Type
+   *
+   * Returning 0 rather than null is historical.
+   *
+   * @param int $columnNumber
+   *
+   * @return int
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getLocationTypeID($columnNumber) {
+    return $this->getMappingFields()[$columnNumber]['location_type_id'] ?? 0;
+  }
+
+  /**
+   * Get the IM or Phone type.
+   *
+   * We have a field that would be the 'relevant' type - which could be either.
+   *
+   * @param int $columnNumber
+   *
+   * @return int
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getPhoneOrIMTypeID($columnNumber) {
+    return $this->getIMProviderID($columnNumber) ?? $this->getPhoneTypeID($columnNumber);
+  }
+
   /**
    * Get the location types of the mapped fields.
+   *
+   * @throws \CiviCRM_API3_Exception
    */
   public function getFieldLocationTypes() {
     return CRM_Utils_Array::collect('location_type_id', $this->getMappingFields());
@@ -72,6 +366,8 @@ class CRM_Import_ImportProcessor {
 
   /**
    * Get the phone types of the mapped fields.
+   *
+   * @throws \CiviCRM_API3_Exception
    */
   public function getFieldPhoneTypes() {
     return CRM_Utils_Array::collect('phone_type_id', $this->getMappingFields());
@@ -79,6 +375,8 @@ class CRM_Import_ImportProcessor {
 
   /**
    * Get the names of the im_provider fields.
+   *
+   * @throws \CiviCRM_API3_Exception
    */
   public function getFieldIMProviderTypes() {
     return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
@@ -86,6 +384,8 @@ class CRM_Import_ImportProcessor {
 
   /**
    * Get the names of the website fields.
+   *
+   * @throws \CiviCRM_API3_Exception
    */
   public function getFieldWebsiteTypes() {
     return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
@@ -95,6 +395,8 @@ class CRM_Import_ImportProcessor {
    * Get an instance of the importer object.
    *
    * @return CRM_Contact_Import_Parser_Contact
+   *
+   * @throws \CiviCRM_API3_Exception
    */
   public function getImporterObject() {
     $importer = new CRM_Contact_Import_Parser_Contact(
@@ -118,4 +420,151 @@ class CRM_Import_ImportProcessor {
     return $importer;
   }
 
+  /**
+   * Load the mapping from the datbase into the format that would be received from the UI.
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  protected function loadSavedMapping() {
+    $fields = civicrm_api3('MappingField', 'get', [
+      'mapping_id' => $this->getMappingID(),
+      'options' => ['limit' => 0],
+    ])['values'];
+    foreach ($fields as $index => $field) {
+      // Fix up the fact that for lost reasons we save by label not name.
+      $fields[$index]['label'] = $field['name'];
+      if (empty($field['relationship_type_id'])) {
+        $fields[$index]['name'] = $this->getNameFromLabel($field['name']);
+      }
+      else {
+        // Honour legacy chaos factor.
+        if ($field['name'] === ts('- do not import -')) {
+          // This is why we save names not labels people....
+          $field['name'] = 'do_not_import';
+        }
+        $fields[$index]['name'] = strtolower(str_replace(" ", "_", $field['name']));
+        // fix for edge cases, CRM-4954
+        if ($fields[$index]['name'] === 'image_url') {
+          $fields[$index]['name'] = str_replace('url', 'URL', $fields[$index]['name']);
+        }
+      }
+      $fieldSpec = $this->getMetadata()[$fields[$index]['name']];
+      if (empty($field['location_type_id']) && !empty($fieldSpec['hasLocationType'])) {
+        $fields[$index]['location_type_id'] = 'Primary';
+      }
+    }
+    $this->mappingFields = $this->rekeyBySortedColumnNumbers($fields);
+  }
+
+  /**
+   * Get the titles from metadata.
+   */
+  public function getMetadataTitles() {
+    if (empty($this->metadataByTitle)) {
+      $this->metadataByTitle = CRM_Utils_Array::collect('title', $this->getMetadata());
+    }
+    return $this->metadataByTitle;
+  }
+
+  /**
+   * Rekey the array by the column_number.
+   *
+   * @param array $mappingFields
+   *
+   * @return array
+   */
+  protected function rekeyBySortedColumnNumbers(array $mappingFields) {
+    $this->mappingFields = CRM_Utils_Array::rekey($mappingFields, 'column_number');
+    ksort($this->mappingFields);
+    return $this->mappingFields;
+  }
+
+  /**
+   * Get the field name from the label.
+   *
+   * @param string $label
+   *
+   * @return string
+   */
+  protected function getNameFromLabel($label) {
+    $titleMap = array_flip($this->getMetadataTitles());
+    return $titleMap[$label] ?? '';
+  }
+
+  /**
+   * Validate the key against the relationships available for the contatct type & subtype.
+   *
+   * @param string $key
+   *
+   * @return bool
+   */
+  protected function isValidRelationshipKey($key) {
+    return !empty($this->getValidRelationships()[$key]) ? TRUE : FALSE;
+  }
+
+  /**
+   * Get the relevant js for quickform.
+   *
+   * @param int $column
+   *
+   * @return string
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getQuickFormJSForField($column) {
+    $columnNumbersToHide = [];
+    if ($this->getFieldName($column) === 'do_not_import') {
+      $columnNumbersToHide = [1, 2, 3];
+    }
+    elseif ($this->getRelationshipKey($column)) {
+      if (!$this->getWebsiteTypeID($column) && !$this->getLocationTypeID($column)) {
+        $columnNumbersToHide[] = 2;
+      }
+      if (!$this->getFieldName($column)) {
+        $columnNumbersToHide[] = 1;
+      }
+      if (!$this->getPhoneOrIMTypeID($column)) {
+        $columnNumbersToHide[] = 3;
+      }
+    }
+    else {
+      if (!$this->getLocationTypeID($column) && !$this->getWebsiteTypeID($column)) {
+        $columnNumbersToHide[] = 1;
+      }
+      if (!$this->getPhoneOrIMTypeID($column)) {
+        $columnNumbersToHide[] = 2;
+      }
+      $columnNumbersToHide[] = 3;
+    }
+
+    $jsClauses = [];
+    foreach ($columnNumbersToHide as $columnNumber) {
+      $jsClauses[] = $this->getFormName() . "['mapper[$column][" . $columnNumber . "]'].style.display = 'none';";
+    }
+    return empty($jsClauses) ? '' : implode("\n", $jsClauses) . "\n";
+  }
+
+  /**
+   * Get the defaults for the column from the saved mapping.
+   *
+   * @param int $column
+   *
+   * @return array
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function getSavedQuickformDefaultsForColumn($column) {
+    if ($this->getFieldName($column) === 'do_not_import') {
+      return [];
+    }
+    if ($this->getValidRelationshipKey($column)) {
+      if ($this->getWebsiteTypeID($column)) {
+        return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getWebsiteTypeID($column)];
+      }
+      return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
+    }
+    if ($this->getWebsiteTypeID($column)) {
+      return [$this->getFieldName($column), $this->getWebsiteTypeID($column)];
+    }
+    return [(string) $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
+  }
+
 }