Merge pull request #12683 from civicrm/5.5
authorEileen McNaughton <eileen@mcnaughty.com>
Fri, 17 Aug 2018 07:51:31 +0000 (19:51 +1200)
committerGitHub <noreply@github.com>
Fri, 17 Aug 2018 07:51:31 +0000 (19:51 +1200)
5.5

1  2 
CRM/Export/BAO/Export.php
CRM/Export/BAO/ExportProcessor.php
tests/phpunit/CRM/Export/BAO/ExportTest.php

index 330427110e62fccc93e6621a51cd9fdeba0d9f70,f248f51a77b028b53d664a9050e81cd24721a6ca..651fc0f6026b37b89b90859d7ad87ac2f5fa1544
@@@ -41,6 -41,24 +41,6 @@@ class CRM_Export_BAO_Export 
    // CRM-7675
    const EXPORT_ROW_COUNT = 100000;
  
 -  /**
 -   * Key representing the head of household in the relationship array.
 -   *
 -   * e.g. 8_a_b.
 -   *
 -   * @var string
 -   */
 -  protected static $headOfHouseholdRelationshipKey;
 -
 -  /**
 -   * Key representing the head of household in the relationship array.
 -   *
 -   * e.g. 8_a_b.
 -   *
 -   * @var string
 -   */
 -  protected static $memberOfHouseholdRelationshipKey;
 -
    /**
     * Key representing the head of household in the relationship array.
     *
  
    /**
     * Get Query Group By Clause
 -   * @param int $exportMode
 +   * @param \CRM_Export_BAO_ExportProcessor $processor
     *   Export Mode
 -   * @param string $queryMode
 -   *   Query Mode
     * @param array $returnProperties
     *   Return Properties
     * @param object $query
     * @return string $groupBy
     *   Group By Clause
     */
 -  public static function getGroupBy($exportMode, $queryMode, $returnProperties, $query) {
 +  public static function getGroupBy($processor, $returnProperties, $query) {
      $groupBy = '';
 +    $exportMode = $processor->getExportMode();
 +    $queryMode = $processor->getQueryMode();
      if (!empty($returnProperties['tags']) || !empty($returnProperties['groups']) ||
        CRM_Utils_Array::value('notes', $returnProperties) ||
        // CRM-9552
      $queryOperator = 'AND'
    ) {
  
 -    $processor = new CRM_Export_BAO_ExportProcessor($exportMode, $fields, $queryOperator);
 +    $processor = new CRM_Export_BAO_ExportProcessor($exportMode, $fields, $queryOperator, $mergeSameHousehold);
      $returnProperties = array();
  
      $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
      // without manually testing the export of IM provider still works.
      $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
      self::$relationshipTypes = $processor->getRelationshipTypes();
 -    //also merge Head of Household
 -    self::$memberOfHouseholdRelationshipKey = CRM_Utils_Array::key('Household Member of', self::$relationshipTypes);
 -    self::$headOfHouseholdRelationshipKey = CRM_Utils_Array::key('Head of Household for', self::$relationshipTypes);
  
      $queryMode = $processor->getQueryMode();
  
      if ($fields) {
-       //construct return properties
-       $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
        foreach ($fields as $key => $value) {
          $fieldName = CRM_Utils_Array::value(1, $value);
          if (!$fieldName) {
            $returnProperties[$fieldName] = $processor->setRelationshipReturnProperties($value, $fieldName);
          }
          elseif (is_numeric(CRM_Utils_Array::value(2, $value))) {
-           $locTypeId = $value[2];
+           $locationName = CRM_Core_PseudoConstant::getName('CRM_Core_BAO_Address', 'location_type_id', $value[2]);
            if ($fieldName == 'phone') {
-             $returnProperties['location'][$locationTypes[$locTypeId]]['phone-' . CRM_Utils_Array::value(3, $value)] = 1;
+             $returnProperties['location'][$locationName]['phone-' . CRM_Utils_Array::value(3, $value)] = 1;
            }
            elseif ($fieldName == 'im') {
-             $returnProperties['location'][$locationTypes[$locTypeId]]['im-' . CRM_Utils_Array::value(3, $value)] = 1;
+             $returnProperties['location'][$locationName]['im-' . CRM_Utils_Array::value(3, $value)] = 1;
            }
            else {
-             $returnProperties['location'][$locationTypes[$locTypeId]][$fieldName] = 1;
+             $returnProperties['location'][$locationName][$fieldName] = 1;
            }
          }
          else {
@@@ -355,16 -373,18 +352,16 @@@ INSERT INTO {$componentTable} SELECT di
  
        foreach ($returnProperties as $key => $value) {
          if (!$processor->isRelationshipTypeKey($key)) {
 -          $returnProperties[self::$memberOfHouseholdRelationshipKey][$key] = $value;
 -          $returnProperties[self::$headOfHouseholdRelationshipKey][$key] = $value;
 +          foreach ($processor->getHouseholdRelationshipTypes() as $householdRelationshipType) {
 +            if (!in_array($key, ['location_type', 'im_provider'])) {
 +              $returnProperties[$householdRelationshipType][$key] = $value;
 +            }
 +          }
          }
        }
 -
 -      unset($returnProperties[self::$memberOfHouseholdRelationshipKey]['location_type']);
 -      unset($returnProperties[self::$memberOfHouseholdRelationshipKey]['im_provider']);
 -      unset($returnProperties[self::$headOfHouseholdRelationshipKey]['location_type']);
 -      unset($returnProperties[self::$headOfHouseholdRelationshipKey]['im_provider']);
      }
  
 -    list($relationQuery, $allRelContactArray) = self::buildRelatedContactArray($selectAll, $ids, $exportMode, $componentTable, $returnProperties, $queryMode);
 +    list($relationQuery, $allRelContactArray) = self::buildRelatedContactArray($selectAll, $ids, $processor, $componentTable, $returnProperties);
  
      // make sure the groups stuff is included only if specifically specified
      // by the fields param (CRM-1969), else we limit the contacts outputted to only
  
      $queryString = "$select $from $where $having";
  
 -    $groupBy = self::getGroupBy($exportMode, $queryMode, $returnProperties, $query);
 +    $groupBy = self::getGroupBy($processor, $returnProperties, $query);
  
      $queryString .= $groupBy;
  
  
        // merge the records if they have corresponding households
        if ($mergeSameHousehold) {
 -        self::mergeSameHousehold($exportTempTable, $headerRows, $sqlColumns, self::$memberOfHouseholdRelationshipKey);
 -        self::mergeSameHousehold($exportTempTable, $headerRows, $sqlColumns, self::$headOfHouseholdRelationshipKey);
 +        foreach ($processor->getHouseholdRelationshipTypes() as $householdRelationshipType) {
 +          self::mergeSameHousehold($exportTempTable, $sqlColumns, $householdRelationshipType);
 +        }
        }
  
        // call export hook
          self::writeCSVFromTable($exportTempTable, $headerRows, $sqlColumns, $exportMode);
        }
        else {
 -        // return tableName and sqlColumns in test context
 +        // return tableName sqlColumns headerRows in test context
          return array($exportTempTable, $sqlColumns, $headerRows);
        }
  
@@@ -1149,15 -1168,18 +1146,15 @@@ WHERE  id IN ( $deleteIDString 
     *
     * @param string $exportTempTable
     *   Temporary temp table that stores the records.
 -   * @param array $headerRows
 -   *   Array of headers for the export file.
     * @param array $sqlColumns
     *   Array of names of the table columns of the temp table.
     * @param string $prefix
     *   Name of the relationship type that is prefixed to the table columns.
     */
 -  public static function mergeSameHousehold($exportTempTable, &$headerRows, &$sqlColumns, $prefix) {
 +  public static function mergeSameHousehold($exportTempTable, &$sqlColumns, $prefix) {
      $prefixColumn = $prefix . '_';
      $allKeys = array_keys($sqlColumns);
      $replaced = array();
 -    $headerRows = array_values($headerRows);
  
      // name map of the non standard fields in header rows & sql columns
      $mappingFields = array(
      foreach ($replaced as $from => $to) {
        $clause[] = "$from = $to ";
        unset($sqlColumns[$to]);
 -      if ($key = CRM_Utils_Array::key($to, $allKeys)) {
 -        unset($headerRows[$key]);
 -      }
      }
      $query .= implode(",\n", $clause);
      $query .= " WHERE {$replaced['civicrm_primary_id']} != ''";
@@@ -1377,19 -1402,18 +1374,19 @@@ WHERE  {$whereClause}"
     *   Columns to go in the temp table.
     * @param \CRM_Export_BAO_ExportProcessor $processor
     * @param array|string $value
 -   * @param array $phoneTypes
 -   * @param array $imProviders
     *
     * @return array
     */
 -  public static function setHeaderRows($field, $headerRows, $sqlColumns, $processor, $value, $phoneTypes, $imProviders) {
 +  public static function setHeaderRows($field, $headerRows, $sqlColumns, $processor, $value) {
  
      $queryFields = $processor->getQueryFields();
      if (substr($field, -11) == 'campaign_id') {
        // @todo - set this correctly in the xml rather than here.
        $headerRows[] = ts('Campaign ID');
      }
 +    elseif ($processor->isMergeSameHousehold() && $field === 'id') {
 +      $headerRows[] = ts('Household ID');
 +    }
      elseif (isset($queryFields[$field]['title'])) {
        $headerRows[] = $queryFields[$field]['title'];
      }
              }
            }
  
 -          $headerRows[] = $headerName;
 +          if (!$processor->isHouseholdMergeRelationshipTypeKey($field)) {
 +            // Do not add to header row if we are only generating for merge reasons.
 +            $headerRows[] = $headerName;
 +          }
  
            self::sqlColumnDefn($processor, $sqlColumns, $headerName);
          }
  
                if (!empty($type[1])) {
                  if (CRM_Utils_Array::value(0, $type) == 'phone') {
 -                  $hdr .= "-" . CRM_Utils_Array::value($type[1], $phoneTypes);
 +                  $hdr .= "-" . CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Phone', 'phone_type_id', $type[1]);
                  }
                  elseif (CRM_Utils_Array::value(0, $type) == 'im') {
 -                  $hdr .= "-" . CRM_Utils_Array::value($type[1], $imProviders);
 +                  $hdr .= "-" . CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_IM', 'provider_id', $type[1]);
                  }
                }
                $headerName = $field . '-' . $hdr;
      $metadata = $headerRows = $outputColumns = $sqlColumns = array();
      $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
      $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
 -
      $queryFields = $processor->getQueryFields();
      foreach ($returnProperties as $key => $value) {
        if ($key != 'location' || !is_array($value)) {
          $outputColumns[$key] = $value;
 -        list($headerRows, $sqlColumns) = self::setHeaderRows($key, $headerRows, $sqlColumns, $processor, $value, $phoneTypes, $imProviders);
 +        list($headerRows, $sqlColumns) = self::setHeaderRows($key, $headerRows, $sqlColumns, $processor, $value);
        }
        else {
          foreach ($value as $locationType => $locationFields) {
                $metadata[$daoFieldName]['pseudoconstant']['var'] = 'imProviders';
              }
              self::sqlColumnDefn($processor, $sqlColumns, $outputFieldName);
 -            list($headerRows, $sqlColumns) = self::setHeaderRows($outputFieldName, $headerRows, $sqlColumns, $processor, $value, $phoneTypes, $imProviders);
 +            list($headerRows, $sqlColumns) = self::setHeaderRows($outputFieldName, $headerRows, $sqlColumns, $processor, $value);
              if ($actualDBFieldName == 'country' || $actualDBFieldName == 'world_region') {
                $metadata[$daoFieldName] = array('context' => 'country');
              }
        }
        elseif (is_array($relationValue) && $relationField == 'location') {
          foreach ($relationValue as $ltype => $val) {
+           // If the location name has a space in it the we need to handle that. This
+           // is kinda hacky but specifically covered in the ExportTest so later efforts to
+           // improve it should be secure in the knowled it will be caught.
+           $ltype = str_replace(' ', '_', $ltype);
            foreach (array_keys($val) as $fld) {
              $type = explode('-', $fld);
              $fldValue = "{$ltype}-" . $type[0];
    /**
     * @param $selectAll
     * @param $ids
 -   * @param $exportMode
 +   * @param \CRM_Export_BAO_ExportProcessor $processor
     * @param $componentTable
     * @param $returnProperties
 -   * @param $queryMode
 +   *
     * @return array
     */
 -  protected static function buildRelatedContactArray($selectAll, $ids, $exportMode, $componentTable, $returnProperties, $queryMode) {
 +  protected static function buildRelatedContactArray($selectAll, $ids, $processor, $componentTable, $returnProperties) {
      $allRelContactArray = $relationQuery = array();
 -
 +    $queryMode = $processor->getQueryMode();
 +    $exportMode = $processor->getExportMode();
      foreach (self::$relationshipTypes as $rel => $dnt) {
        if ($relationReturnProperties = CRM_Utils_Array::value($rel, $returnProperties)) {
          $allRelContactArray[$rel] = array();
index 6c82c5ce8579e5f115b162106baf8705e781d7a7,574941470439751ce8c763c613e1c6c0cf23a44e..b038424a4daabff8b1cfbf25f5cf94e53301463f
@@@ -72,13 -72,6 +72,13 @@@ class CRM_Export_BAO_ExportProcessor 
     */
    protected $requestedFields;
  
 +  /**
 +   * Is the contact being merged into a single household.
 +   *
 +   * @var bool
 +   */
 +  protected $isMergeSameHousehold;
 +
    /**
     * Key representing the head of household in the relationship array.
     *
     * @param int $exportMode
     * @param array|NULL $requestedFields
     * @param string $queryOperator
 +   * @param bool $isMergeSameHousehold
     */
 -  public function __construct($exportMode, $requestedFields, $queryOperator) {
 +  public function __construct($exportMode, $requestedFields, $queryOperator, $isMergeSameHousehold = FALSE) {
      $this->setExportMode($exportMode);
      $this->setQueryMode();
      $this->setQueryOperator($queryOperator);
      $this->setRequestedFields($requestedFields);
      $this->setRelationshipTypes();
 +    $this->setIsMergeSameHousehold($isMergeSameHousehold);
    }
  
    /**
      );
    }
  
 +  /**
 +   * @return bool
 +   */
 +  public function isMergeSameHousehold() {
 +    return $this->isMergeSameHousehold;
 +  }
 +
 +  /**
 +   * @param bool $isMergeSameHousehold
 +   */
 +  public function setIsMergeSameHousehold($isMergeSameHousehold) {
 +    $this->isMergeSameHousehold = $isMergeSameHousehold;
 +  }
 +
 +  /**
 +   * Return relationship types for household merge.
 +   *
 +   * @return mixed
 +   */
 +  public function getHouseholdRelationshipTypes() {
 +    if (!$this->isMergeSameHousehold()) {
 +      return [];
 +    }
 +    return [
 +      CRM_Utils_Array::key('Household Member of', $this->getRelationshipTypes()),
 +      CRM_Utils_Array::key('Head of Household for', $this->getRelationshipTypes()),
 +    ];
 +  }
  
    /**
     * @param $fieldName
      return array_key_exists($fieldName, $this->relationshipTypes);
    }
  
 +
 +  /**
 +   * @param $fieldName
 +   * @return bool
 +   */
 +  public function isHouseholdMergeRelationshipTypeKey($fieldName) {
 +    return in_array($fieldName, $this->getHouseholdRelationshipTypes());
 +  }
 +
    /**
     * @return string
     */
     * @return array
     */
    public function setRelationshipReturnProperties($value, $relationshipKey) {
-     $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
      $relPhoneTypeId = $relIMProviderId = NULL;
      if (!empty($value[2])) {
        $relationField = CRM_Utils_Array::value(2, $value);
        }
      }
      if (in_array($relationField, $this->getValidLocationFields()) && is_numeric($relLocTypeId)) {
+       $locationName = CRM_Core_PseudoConstant::getName('CRM_Core_BAO_Address', 'location_type_id', $relLocTypeId);
        if ($relPhoneTypeId) {
-         $this->relationshipReturnProperties[$relationshipKey]['location'][$locationTypes[$relLocTypeId]]['phone-' . $relPhoneTypeId] = 1;
+         $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName]['phone-' . $relPhoneTypeId] = 1;
        }
        elseif ($relIMProviderId) {
-         $this->relationshipReturnProperties[$relationshipKey]['location'][$locationTypes[$relLocTypeId]]['im-' . $relIMProviderId] = 1;
+         $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName]['im-' . $relIMProviderId] = 1;
        }
        else {
-         $this->relationshipReturnProperties[$relationshipKey]['location'][$locationTypes[$relLocTypeId]][$relationField] = 1;
+         $this->relationshipReturnProperties[$relationshipKey]['location'][$locationName][$relationField] = 1;
        }
      }
      else {
index 7fe095b7f1b7f6849ef803ad02eadce3556c1210,e6f60a8d8dd288ba9e9a7b0786fc262e4892fc6f..cea24c8dcac36291d3c8d63c22beb59bdf42e629
@@@ -42,6 -42,8 +42,8 @@@ class CRM_Export_BAO_ExportTest extend
     */
    protected $masterAddressID;
  
+   protected $locationTypes = [];
    public function tearDown() {
      $this->quickCleanup([
        'civicrm_contact',
        'civicrm_case_activity',
      ]);
      $this->quickCleanUpFinancialEntities();
+     if (!empty($this->locationTypes)) {
+       $this->callAPISuccess('LocationType', 'delete', ['id' => $this->locationTypes['Whare Kai']['id']]);
+       $this->callAPISuccess('LocationType', 'create', ['id' => $this->locationTypes['Main']['id'], 'name' => 'Main']);
+     }
      parent::tearDown();
    }
  
        ['Individual', 'city', ''],
        ['Individual', 'state_province', ''],
      ];
 -    list($tableName) = CRM_Export_BAO_Export::exportComponents(
 +    list($tableName, $sqlColumns, $headerRows) = CRM_Export_BAO_Export::exportComponents(
        FALSE,
        $this->contactIDs,
        [],
        $this->assertEquals($householdID, $dao->civicrm_primary_id);
      }
  
 +    $this->assertEquals([
 +      0 => 'City',
 +      1 => 'State',
 +      2 => 'Household ID',
 +    ], $headerRows);
 +    $this->assertEquals(
 +      [
 +        'city' => 'city varchar(64)',
 +        'state_province' => 'state_province varchar(64)',
 +        'civicrm_primary_id' => 'civicrm_primary_id varchar(16)',
 +      ], $sqlColumns);
    }
  
    /**
    public function testExportIMData() {
      // Use default providers.
      $providers = ['AIM', 'GTalk', 'Jabber', 'MSN', 'Skype', 'Yahoo'];
-     $locationTypes = ['Billing', 'Home', 'Main', 'Other'];
+     // Main sure labels are not all anglo chars.
+     $this->diversifyLocationTypes();
+     $locationTypes = ['Billing' => 'Billing', 'Home' => 'Home', 'Main' => 'Méin', 'Other' => 'Other', 'Whare Kai' => 'Whare Kai'];
  
      $this->contactIDs[] = $this->individualCreate();
      $this->contactIDs[] = $this->individualCreate();
      $this->contactIDs[] = $this->organizationCreate();
      foreach ($this->contactIDs as $contactID) {
        foreach ($providers as $provider) {
-         foreach ($locationTypes as $locationType) {
+         foreach ($locationTypes as $locationName => $locationLabel) {
            $this->callAPISuccess('IM', 'create', [
              'contact_id' => $contactID,
-             'location_type_id' => $locationType,
+             'location_type_id' => $locationName,
              'provider_id' => $provider,
-             'name' => $locationType . $provider . $contactID,
+             'name' => $locationName . $provider . $contactID,
            ]);
          }
        }
  
      $fields = [['Individual', 'contact_id']];
      // ' ' denotes primary location type.
-     foreach (array_merge($locationTypes, [' ']) as $locationType) {
+     foreach (array_keys(array_merge($locationTypes, [' ' => ['Primary']])) as $locationType) {
        $fields[] = [
          'Individual',
          'im_provider',
        '5_a_b_other_im_screen_name_skype' => '5_a_b_other_im_screen_name_skype text',
        '5_a_b_other_im_screen_name_yahoo' => '5_a_b_other_im_screen_name_yahoo text',
        '5_a_b_im' => '5_a_b_im text',
+       'whare_kai_im_provider' => 'whare_kai_im_provider text',
+       'whare_kai_im_screen_name' => 'whare_kai_im_screen_name text',
+       'whare_kai_im_screen_name_jabber' => 'whare_kai_im_screen_name_jabber text',
+       'whare_kai_im_screen_name_skype' => 'whare_kai_im_screen_name_skype text',
+       'whare_kai_im_screen_name_yahoo' => 'whare_kai_im_screen_name_yahoo text',
+       '2_a_b_whare_kai_im_screen_name' => '2_a_b_whare_kai_im_screen_name text',
+       '2_a_b_whare_kai_im_screen_name_jabber' => '2_a_b_whare_kai_im_screen_name_jabber text',
+       '2_a_b_whare_kai_im_screen_name_skype' => '2_a_b_whare_kai_im_screen_name_skype text',
+       '2_a_b_whare_kai_im_screen_name_yahoo' => '2_a_b_whare_kai_im_screen_name_yahoo text',
+       '8_a_b_whare_kai_im_screen_name' => '8_a_b_whare_kai_im_screen_name text',
+       '8_a_b_whare_kai_im_screen_name_jabber' => '8_a_b_whare_kai_im_screen_name_jabber text',
+       '8_a_b_whare_kai_im_screen_name_skype' => '8_a_b_whare_kai_im_screen_name_skype text',
+       '8_a_b_whare_kai_im_screen_name_yahoo' => '8_a_b_whare_kai_im_screen_name_yahoo text',
+       '5_a_b_whare_kai_im_screen_name' => '5_a_b_whare_kai_im_screen_name text',
+       '5_a_b_whare_kai_im_screen_name_jabber' => '5_a_b_whare_kai_im_screen_name_jabber text',
+       '5_a_b_whare_kai_im_screen_name_skype' => '5_a_b_whare_kai_im_screen_name_skype text',
+       '5_a_b_whare_kai_im_screen_name_yahoo' => '5_a_b_whare_kai_im_screen_name_yahoo text',
      ], $sqlColumns);
  
    }
  
+   /**
+    * Export City against multiple location types.
+    */
+   public function testExportAddressData() {
+     $this->diversifyLocationTypes();
+     $locationTypes = ['Billing' => 'Billing', 'Home' => 'Home', 'Main' => 'Méin', 'Other' => 'Other', 'Whare Kai' => 'Whare Kai'];
+     $this->contactIDs[] = $this->individualCreate();
+     $this->contactIDs[] = $this->individualCreate();
+     $this->contactIDs[] = $this->householdCreate();
+     $this->contactIDs[] = $this->organizationCreate();
+     $fields = [['Individual', 'contact_id']];
+     foreach ($this->contactIDs as $contactID) {
+       foreach ($locationTypes as $locationName => $locationLabel) {
+         $this->callAPISuccess('Address', 'create', [
+           'contact_id' => $contactID,
+           'location_type_id' => $locationName,
+           'street_address' => $locationLabel . $contactID . 'street_address',
+           'city' => $locationLabel . $contactID . 'city',
+           'postal_code' => $locationLabel . $contactID . 'postal_code',
+         ]);
+         $fields[] = ['Individual', 'city', CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Address', 'location_type_id', $locationName)];
+         $fields[] = ['Individual', 'street_address', CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Address', 'location_type_id', $locationName)];
+         $fields[] = ['Individual', 'postal_code', CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Address', 'location_type_id', $locationName)];
+       }
+     }
+     $relationships = [
+       $this->contactIDs[1] => ['label' => 'Spouse of'],
+       $this->contactIDs[2] => ['label' => 'Household Member of'],
+       $this->contactIDs[3] => ['label' => 'Employee of']
+     ];
+     foreach ($relationships as $contactID => $relationshipType) {
+       $relationshipTypeID = $this->callAPISuccess('RelationshipType', 'getvalue', ['label_a_b' => $relationshipType['label'], 'return' => 'id']);
+       $result = $this->callAPISuccess('Relationship', 'create', [
+         'contact_id_a' => $this->contactIDs[0],
+         'relationship_type_id' => $relationshipTypeID,
+         'contact_id_b' => $contactID
+       ]);
+       $relationships[$contactID]['id'] = $result['id'];
+       $relationships[$contactID]['relationship_type_id'] = $relationshipTypeID;
+     }
+     // ' ' denotes primary location type.
+     foreach (array_keys(array_merge($locationTypes, [' ' => ['Primary']])) as $locationType) {
+       foreach ($relationships as $contactID => $relationship) {
+         $fields[] = [
+           'Individual',
+           $relationship['relationship_type_id'] . '_a_b',
+           'city',
+           CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_IM', 'location_type_id', $locationType),
+         ];
+       }
+     }
+     list($tableName, $sqlColumns) = $this->doExport($fields, $this->contactIDs[0]);
+     $dao = CRM_Core_DAO::executeQuery('SELECT * FROM ' . $tableName);
+     while ($dao->fetch()) {
+       $id = $dao->contact_id;
+       $this->assertEquals('Méin' . $id . 'city', $dao->main_city);
+       $this->assertEquals('Billing' . $id . 'street_address', $dao->billing_street_address);
+       $this->assertEquals('Whare Kai' . $id . 'postal_code', $dao->whare_kai_postal_code);
+       foreach ($relationships as $relatedContactID => $relationship) {
+         $relationshipString = $field = $relationship['relationship_type_id'] . '_a_b';
+         $field = $relationshipString . '_main_city';
+         $this->assertEquals('Méin' . $relatedContactID . 'city', $dao->$field);
+       }
+     }
+     $this->assertEquals([
+       'contact_id' => 'contact_id varchar(255)',
+       'billing_city' => 'billing_city text',
+       'billing_street_address' => 'billing_street_address text',
+       'billing_postal_code' => 'billing_postal_code text',
+       'home_city' => 'home_city text',
+       'home_street_address' => 'home_street_address text',
+       'home_postal_code' => 'home_postal_code text',
+       'main_city' => 'main_city text',
+       'main_street_address' => 'main_street_address text',
+       'main_postal_code' => 'main_postal_code text',
+       'other_city' => 'other_city text',
+       'other_street_address' => 'other_street_address text',
+       'other_postal_code' => 'other_postal_code text',
+       'whare_kai_city' => 'whare_kai_city text',
+       'whare_kai_street_address' => 'whare_kai_street_address text',
+       'whare_kai_postal_code' => 'whare_kai_postal_code text',
+       '2_a_b_billing_city' => '2_a_b_billing_city text',
+       '2_a_b_home_city' => '2_a_b_home_city text',
+       '2_a_b_main_city' => '2_a_b_main_city text',
+       '2_a_b_other_city' => '2_a_b_other_city text',
+       '2_a_b_whare_kai_city' => '2_a_b_whare_kai_city text',
+       '2_a_b_city' => '2_a_b_city text',
+       '8_a_b_billing_city' => '8_a_b_billing_city text',
+       '8_a_b_home_city' => '8_a_b_home_city text',
+       '8_a_b_main_city' => '8_a_b_main_city text',
+       '8_a_b_other_city' => '8_a_b_other_city text',
+       '8_a_b_whare_kai_city' => '8_a_b_whare_kai_city text',
+       '8_a_b_city' => '8_a_b_city text',
+       '5_a_b_billing_city' => '5_a_b_billing_city text',
+       '5_a_b_home_city' => '5_a_b_home_city text',
+       '5_a_b_main_city' => '5_a_b_main_city text',
+       '5_a_b_other_city' => '5_a_b_other_city text',
+       '5_a_b_whare_kai_city' => '5_a_b_whare_kai_city text',
+       '5_a_b_city' => '5_a_b_city text',
+     ], $sqlColumns);
+   }
    /**
     * Test master_address_id field.
     */
      ];
    }
  
+   /**
+    * Change our location types so we have some edge cases in the mix.
+    *
+    * - a space in the name
+    * - name differs from label
+    * - non-anglo char in the label (not valid in the name).
+    */
+   protected function diversifyLocationTypes() {
+     $this->locationTypes['Main'] = $this->callAPISuccess('Location_type', 'get', [
+       'name' => 'Main',
+       'return' => 'id',
+       'api.LocationType.Create' => ['display_name' => 'Méin'],
+     ]);
+     $this->locationTypes['Whare Kai'] = $this->callAPISuccess('Location_type', 'create', [
+       'name' => 'Whare Kai',
+       'display_name' => 'Whare Kai',
+     ]);
+   }
  }