Fix Export when full group by mode is used
[civicrm-core.git] / CRM / Export / BAO / Export.php
index fec624e51fd6431b07cb02ebd0bcd75956c9f766..c56ca09031a3e90cd3b6ac6af59c833085a04e2b 100644 (file)
@@ -166,14 +166,7 @@ class CRM_Export_BAO_Export {
       $groupBy = "civicrm_activity.id ";
     }
 
-    if (!empty($groupBy)) {
-      if (!Civi::settings()->get('searchPrimaryDetailsOnly')) {
-        CRM_Core_DAO::disableFullGroupByMode();
-      }
-      $groupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($query->_select, $groupBy);
-    }
-
-    return $groupBy;
+    return $groupBy ? ' GROUP BY ' . $groupBy : '';
   }
 
   /**
@@ -228,15 +221,8 @@ class CRM_Export_BAO_Export {
 
     $processor = new CRM_Export_BAO_ExportProcessor($exportMode, $fields, $queryOperator, $mergeSameHousehold);
     $returnProperties = array();
-
-    $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
-    // Warning - this imProviders var is used in a somewhat fragile way - don't rename it
-    // 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();
 
-    $queryMode = $processor->getQueryMode();
-
     if ($fields) {
       foreach ($fields as $key => $value) {
         $fieldName = CRM_Utils_Array::value(1, $value);
@@ -357,11 +343,13 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
               $returnProperties[$householdRelationshipType][$key] = $value;
             }
           }
+          // @todo - don't use returnProperties above.
+          $processor->setHouseholdMergeReturnProperties($returnProperties[$householdRelationshipType]);
         }
       }
     }
 
-    list($relationQuery, $allRelContactArray) = self::buildRelatedContactArray($selectAll, $ids, $processor, $componentTable, $returnProperties);
+    self::buildRelatedContactArray($selectAll, $ids, $processor, $componentTable);
 
     // 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
@@ -430,29 +418,19 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
 
     $paymentDetails = array();
     if ($processor->isExportPaymentFields()) {
-
       // get payment related in for event and members
       $paymentDetails = CRM_Contribute_BAO_Contribution::getContributionDetails($exportMode, $ids);
       //get all payment headers.
       // If we haven't selected specific payment fields, load in all the
       // payment headers.
       if (!$processor->isExportSpecifiedPaymentFields()) {
-        $paymentHeaders = self::componentPaymentFields();
         if (!empty($paymentDetails)) {
           $addPaymentHeader = TRUE;
         }
       }
-      // If we have selected specific payment fields, leave the payment headers
-      // as an empty array; the headers for each selected field will be added
-      // elsewhere.
-      else {
-        $paymentHeaders = array();
-      }
-      $nullContributionDetails = array_fill_keys(array_keys($paymentHeaders), NULL);
     }
 
     $componentDetails = array();
-    $setHeader = TRUE;
 
     $rowCount = self::EXPORT_ROW_COUNT;
     $offset = 0;
@@ -461,15 +439,26 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
 
     $count = -1;
 
-    // for CRM-3157 purposes
-    $i18n = CRM_Core_I18n::singleton();
-
     list($outputColumns, $headerRows, $sqlColumns, $metadata) = self::getExportStructureArrays($returnProperties, $processor);
 
+    // add payment headers if required
+    if ($addPaymentHeader && $processor->isExportPaymentFields()) {
+      // @todo rather than do this for every single row do it before the loop starts.
+      // where other header definitions take place.
+      $headerRows = array_merge($headerRows, $processor->getPaymentHeaders());
+      foreach (array_keys($processor->getPaymentHeaders()) as $paymentHdr) {
+        self::sqlColumnDefn($processor, $sqlColumns, $paymentHdr);
+      }
+    }
+
+    $exportTempTable = self::createTempTable($sqlColumns);
     $limitReached = FALSE;
+
     while (!$limitReached) {
       $limitQuery = "{$queryString} LIMIT {$offset}, {$rowCount}";
+      CRM_Core_DAO::disableFullGroupByMode();
       $iterationDAO = CRM_Core_DAO::executeQuery($limitQuery);
+      CRM_Core_DAO::reenableFullGroupByMode();
       // If this is less than our limit by the end of the iteration we do not need to run the query again to
       // check if some remain.
       $rowsThisIteration = 0;
@@ -477,88 +466,7 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
       while ($iterationDAO->fetch()) {
         $count++;
         $rowsThisIteration++;
-        $row = array();
-        $query->convertToPseudoNames($iterationDAO);
-
-        //first loop through output columns so that we return what is required, and in same order.
-        foreach ($outputColumns as $field => $value) {
-
-          // add im_provider to $dao object
-          if ($field == 'im_provider' && property_exists($iterationDAO, 'provider_id')) {
-            $iterationDAO->im_provider = $iterationDAO->provider_id;
-          }
-
-          //build row values (data)
-          $fieldValue = NULL;
-          if (property_exists($iterationDAO, $field)) {
-            $fieldValue = $iterationDAO->$field;
-            // to get phone type from phone type id
-            if ($field == 'phone_type_id' && isset($phoneTypes[$fieldValue])) {
-              $fieldValue = $phoneTypes[$fieldValue];
-            }
-            elseif ($field == 'provider_id' || $field == 'im_provider') {
-              $fieldValue = CRM_Utils_Array::value($fieldValue, $imProviders);
-            }
-            elseif (strstr($field, 'master_id')) {
-              $masterAddressId = NULL;
-              if (isset($iterationDAO->$field)) {
-                $masterAddressId = $iterationDAO->$field;
-              }
-              // get display name of contact that address is shared.
-              $fieldValue = CRM_Contact_BAO_Contact::getMasterDisplayName($masterAddressId);
-            }
-          }
-
-          if ($processor->isRelationshipTypeKey($field)) {
-            $relDAO = CRM_Utils_Array::value($iterationDAO->contact_id, $allRelContactArray[$field]);
-            $relationQuery[$field]->convertToPseudoNames($relDAO);
-            self::fetchRelationshipDetails($relDAO, $value, $field, $row);
-          }
-          else {
-            $row[$field] = self::getTransformedFieldValue($field, $iterationDAO, $fieldValue, $i18n, $metadata, $paymentDetails, $processor);
-          }
-        }
-
-        // add payment headers if required
-        if ($addPaymentHeader && $processor->isExportPaymentFields()) {
-          // @todo rather than do this for every single row do it before the loop starts.
-          // where other header definitions take place.
-          $headerRows = array_merge($headerRows, $paymentHeaders);
-          foreach (array_keys($paymentHeaders) as $paymentHdr) {
-            self::sqlColumnDefn($processor, $sqlColumns, $paymentHdr);
-          }
-        }
-
-        if ($setHeader) {
-          $exportTempTable = self::createTempTable($sqlColumns);
-        }
-
-        //build header only once
-        $setHeader = FALSE;
-
-        // If specific payment fields have been selected for export, payment
-        // data will already be in $row. Otherwise, add payment related
-        // information, if appropriate.
-        if ($addPaymentHeader) {
-          if (!$processor->isExportSpecifiedPaymentFields()) {
-            if ($processor->isExportPaymentFields()) {
-              $paymentData = CRM_Utils_Array::value($row[$paymentTableId], $paymentDetails);
-              if (!is_array($paymentData) || empty($paymentData)) {
-                $paymentData = $nullContributionDetails;
-              }
-              $row = array_merge($row, $paymentData);
-            }
-            elseif (!empty($paymentDetails)) {
-              $row = array_merge($row, $nullContributionDetails);
-            }
-          }
-        }
-        //remove organization name for individuals if it is set for current employer
-        if (!empty($row['contact_type']) &&
-          $row['contact_type'] == 'Individual' && array_key_exists('organization_name', $row)
-        ) {
-          $row['organization_name'] = '';
-        }
+        $row = $processor->buildRow($query, $iterationDAO, $outputColumns, $metadata, $paymentDetails, $addPaymentHeader, $paymentTableId);
 
         // add component info
         // write the row to a file
@@ -1110,16 +1018,11 @@ WHERE  id IN ( $deleteIDString )
    */
   public static function mergeSameHousehold($exportTempTable, &$sqlColumns, $prefix) {
     $prefixColumn = $prefix . '_';
-    $allKeys = array_keys($sqlColumns);
     $replaced = array();
 
     // name map of the non standard fields in header rows & sql columns
     $mappingFields = array(
       'civicrm_primary_id' => 'id',
-      'contact_source' => 'source',
-      'current_employer_id' => 'employer_id',
-      'contact_is_deleted' => 'is_deleted',
-      'name' => 'address_name',
       'provider_id' => 'im_service_provider',
       'phone_type_id' => 'phone_type',
     );
@@ -1296,6 +1199,11 @@ WHERE  {$whereClause}";
 
   /**
    * Build componentPayment fields.
+   *
+   * This is no longer used by export but BAO_Mapping still calls it & we
+   * should find a generic way to handle this or move this to that class.
+   *
+   * @deprecated
    */
   public static function componentPaymentFields() {
     static $componentPaymentFields;
@@ -1333,12 +1241,8 @@ WHERE  {$whereClause}";
     elseif (isset($queryFields[$field]['title'])) {
       $headerRows[] = $queryFields[$field]['title'];
     }
-    elseif ($field == 'provider_id') {
-      // @todo - set this correctly in the xml rather than here.
-      $headerRows[] = ts('IM Service Provider');
-    }
-    elseif ($processor->isExportPaymentFields() && array_key_exists($field, self::componentPaymentFields())) {
-      $headerRows[] = CRM_Utils_Array::value($field, self::componentPaymentFields());
+    elseif ($processor->isExportPaymentFields() && array_key_exists($field, $processor->getcomponentPaymentFields())) {
+      $headerRows[] = CRM_Utils_Array::value($field, $processor->getcomponentPaymentFields());
     }
     else {
       $headerRows[] = $field;
@@ -1392,17 +1296,7 @@ WHERE  {$whereClause}";
         foreach ($value as $relationField => $relationValue) {
           // below block is same as primary block (duplicate)
           if (isset($queryFields[$relationField]['title'])) {
-            if ($queryFields[$relationField]['name'] == 'name') {
-              $headerName = $field . '-' . $relationField;
-            }
-            else {
-              if ($relationField == 'current_employer') {
-                $headerName = $field . '-' . 'current_employer';
-              }
-              else {
-                $headerName = $field . '-' . $queryFields[$relationField]['name'];
-              }
-            }
+            $headerName = $field . '-' . $relationField;
 
             if (!$processor->isHouseholdMergeRelationshipTypeKey($field)) {
               // Do not add to header row if we are only generating for merge reasons.
@@ -1416,11 +1310,6 @@ WHERE  {$whereClause}";
             $headerRows[] = $headerName;
             self::sqlColumnDefn($processor, $sqlColumns, $headerName);
           }
-          elseif ($relationField == 'provider_id') {
-            $headerName = $field . '-' . 'Im Service Provider';
-            $headerRows[] = $headerName;
-            self::sqlColumnDefn($processor, $sqlColumns, $headerName);
-          }
           elseif ($relationField == 'state_province_id') {
             $headerName = $field . '-' . 'state_province_id';
             $headerRows[] = $headerName;
@@ -1502,6 +1391,8 @@ WHERE  {$whereClause}";
     $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
     $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
     $i18n = CRM_Core_I18n::singleton();
+    $field = $field . '_';
+
     foreach ($value as $relationField => $relationValue) {
       if (is_object($relDAO) && property_exists($relDAO, $relationField)) {
         $fieldValue = $relDAO->$relationField;
@@ -1532,7 +1423,6 @@ WHERE  {$whereClause}";
       else {
         $fieldValue = '';
       }
-      $field = $field . '_';
       $relPrefix = $field . $relationField;
 
       if (is_object($relDAO) && $relationField == 'id') {
@@ -1649,171 +1539,76 @@ WHERE  {$whereClause}";
    * @param $ids
    * @param \CRM_Export_BAO_ExportProcessor $processor
    * @param $componentTable
-   * @param $returnProperties
-   *
-   * @return array
    */
-  protected static function buildRelatedContactArray($selectAll, $ids, $processor, $componentTable, $returnProperties) {
+  protected static function buildRelatedContactArray($selectAll, $ids, $processor, $componentTable) {
     $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();
-        // build Query for each relationship
-        $relationQuery[$rel] = new CRM_Contact_BAO_Query(NULL, $relationReturnProperties,
-          NULL, FALSE, FALSE, $queryMode
-        );
-        list($relationSelect, $relationFrom, $relationWhere, $relationHaving) = $relationQuery[$rel]->query();
-
-        list($id, $direction) = explode('_', $rel, 2);
-        // identify the relationship direction
-        $contactA = 'contact_id_a';
-        $contactB = 'contact_id_b';
-        if ($direction == 'b_a') {
-          $contactA = 'contact_id_b';
-          $contactB = 'contact_id_a';
-        }
-        $relIDs = self::getIDsForRelatedContact($ids, $exportMode);
 
-        $relationshipJoin = $relationshipClause = '';
-        if (!$selectAll && $componentTable) {
-          $relationshipJoin = " INNER JOIN {$componentTable} ctTable ON ctTable.contact_id = {$contactA}";
-        }
-        elseif (!empty($relIDs)) {
-          $relID = implode(',', $relIDs);
-          $relationshipClause = " AND crel.{$contactA} IN ( {$relID} )";
-        }
+    foreach ($processor->getRelationshipReturnProperties() as $relationshipKey => $relationReturnProperties) {
+      $allRelContactArray[$relationshipKey] = array();
+      // build Query for each relationship
+      $relationQuery = new CRM_Contact_BAO_Query(NULL, $relationReturnProperties,
+        NULL, FALSE, FALSE, $queryMode
+      );
+      list($relationSelect, $relationFrom, $relationWhere, $relationHaving) = $relationQuery->query();
+
+      list($id, $direction) = explode('_', $relationshipKey, 2);
+      // identify the relationship direction
+      $contactA = 'contact_id_a';
+      $contactB = 'contact_id_b';
+      if ($direction == 'b_a') {
+        $contactA = 'contact_id_b';
+        $contactB = 'contact_id_a';
+      }
+      $relIDs = self::getIDsForRelatedContact($ids, $exportMode);
 
-        $relationFrom = " {$relationFrom}
-                INNER JOIN civicrm_relationship crel ON crel.{$contactB} = contact_a.id AND crel.relationship_type_id = {$id}
-                {$relationshipJoin} ";
-
-        //check for active relationship status only
-        $today = date('Ymd');
-        $relationActive = " AND (crel.is_active = 1 AND ( crel.end_date is NULL OR crel.end_date >= {$today} ) )";
-        $relationWhere = " WHERE contact_a.is_deleted = 0 {$relationshipClause} {$relationActive}";
-        $relationGroupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($relationQuery[$rel]->_select, "crel.{$contactA}");
-        $relationSelect = "{$relationSelect}, {$contactA} as refContact ";
-        $relationQueryString = "$relationSelect $relationFrom $relationWhere $relationHaving $relationGroupBy";
-
-        $allRelContactDAO = CRM_Core_DAO::executeQuery($relationQueryString);
-        while ($allRelContactDAO->fetch()) {
-          //FIX Me: Migrate this to table rather than array
-          // build the array of all related contacts
-          $allRelContactArray[$rel][$allRelContactDAO->refContact] = clone($allRelContactDAO);
-        }
-        $allRelContactDAO->free();
+      $relationshipJoin = $relationshipClause = '';
+      if (!$selectAll && $componentTable) {
+        $relationshipJoin = " INNER JOIN {$componentTable} ctTable ON ctTable.contact_id = {$contactA}";
+      }
+      elseif (!empty($relIDs)) {
+        $relID = implode(',', $relIDs);
+        $relationshipClause = " AND crel.{$contactA} IN ( {$relID} )";
       }
-    }
-    return array($relationQuery, $allRelContactArray);
-  }
 
-  /**
-   * @param $field
-   * @param $iterationDAO
-   * @param $fieldValue
-   * @param $i18n
-   * @param $metadata
-   * @param $paymentDetails
-   *
-   * @param \CRM_Export_BAO_ExportProcessor $processor
-   *
-   * @return string
-   */
-  protected static function getTransformedFieldValue($field, $iterationDAO, $fieldValue, $i18n, $metadata, $paymentDetails, $processor) {
+      $relationFrom = " {$relationFrom}
+              INNER JOIN civicrm_relationship crel ON crel.{$contactB} = contact_a.id AND crel.relationship_type_id = {$id}
+              {$relationshipJoin} ";
 
-    if ($field == 'id') {
-      return $iterationDAO->contact_id;
-      // special case for calculated field
-    }
-    elseif ($field == 'source_contact_id') {
-      return $iterationDAO->contact_id;
-    }
-    elseif ($field == 'pledge_balance_amount') {
-      return $iterationDAO->pledge_amount - $iterationDAO->pledge_total_paid;
-      // special case for calculated field
-    }
-    elseif ($field == 'pledge_next_pay_amount') {
-      return $iterationDAO->pledge_next_pay_amount + $iterationDAO->pledge_outstanding_amount;
-    }
-    elseif (isset($fieldValue) &&
-      $fieldValue != ''
-    ) {
-      //check for custom data
-      if ($cfID = CRM_Core_BAO_CustomField::getKeyID($field)) {
-        return CRM_Core_BAO_CustomField::displayValue($fieldValue, $cfID);
-      }
+      //check for active relationship status only
+      $today = date('Ymd');
+      $relationActive = " AND (crel.is_active = 1 AND ( crel.end_date is NULL OR crel.end_date >= {$today} ) )";
+      $relationWhere = " WHERE contact_a.is_deleted = 0 {$relationshipClause} {$relationActive}";
+      CRM_Core_DAO::disableFullGroupByMode();
+      $relationSelect = "{$relationSelect}, {$contactA} as refContact ";
+      $relationQueryString = "$relationSelect $relationFrom $relationWhere $relationHaving GROUP BY crel.{$contactA}";
 
-      elseif (in_array($field, array(
-        'email_greeting',
-        'postal_greeting',
-        'addressee',
-      ))) {
-        //special case for greeting replacement
-        $fldValue = "{$field}_display";
-        return $iterationDAO->$fldValue;
-      }
-      else {
-        //normal fields with a touch of CRM-3157
-        switch ($field) {
-          case 'country':
-          case 'world_region':
-            return $i18n->crm_translate($fieldValue, array('context' => 'country'));
-
-          case 'state_province':
-            return $i18n->crm_translate($fieldValue, array('context' => 'province'));
-
-          case 'gender':
-          case 'preferred_communication_method':
-          case 'preferred_mail_format':
-          case 'communication_style':
-            return $i18n->crm_translate($fieldValue);
-
-          default:
-            if (isset($metadata[$field])) {
-              // No I don't know why we do it this way & whether we could
-              // make better use of pseudoConstants.
-              if (!empty($metadata[$field]['context'])) {
-                return $i18n->crm_translate($fieldValue, $metadata[$field]);
-              }
-              if (!empty($metadata[$field]['pseudoconstant'])) {
-                // This is not our normal syntax for pseudoconstants but I am a bit loath to
-                // call an external function until sure it is not increasing php processing given this
-                // may be iterated 100,000 times & we already have the $imProvider var loaded.
-                // That can be next refactor...
-                // Yes - definitely feeling hatred for this bit of code - I know you will beat me up over it's awfulness
-                // but I have to reach a stable point....
-                $varName = $metadata[$field]['pseudoconstant']['var'];
-                if ($varName === 'imProviders') {
-                  return CRM_Core_PseudoConstant::getLabel('CRM_Core_DAO_IM', 'provider_id', $fieldValue);
-                }
-                if ($varName === 'phoneTypes') {
-                  return CRM_Core_PseudoConstant::getLabel('CRM_Core_DAO_Phone', 'phone_type_id', $fieldValue);
-                }
-              }
+      $allRelContactDAO = CRM_Core_DAO::executeQuery($relationQueryString);
+      CRM_Core_DAO::reenableFullGroupByMode();
 
+      while ($allRelContactDAO->fetch()) {
+        $relationQuery->convertToPseudoNames($allRelContactDAO);
+        $row = [];
+        // @todo pass processor to fetchRelationshipDetails and set fields directly within it.
+        self::fetchRelationshipDetails($allRelContactDAO, $relationReturnProperties, $relationshipKey, $row);
+        foreach (array_keys($relationReturnProperties) as $property) {
+          if ($property === 'location') {
+            // @todo - simplify location in self::fetchRelationshipDetails - remove handling here. Or just call
+            // $processor->setRelationshipValue from fetchRelationshipDetails
+            foreach ($relationReturnProperties['location'] as $locationName => $locationValues) {
+              foreach (array_keys($locationValues) as $locationValue) {
+                $key = str_replace(' ', '_', $locationName) . '-' . $locationValue;
+                $processor->setRelationshipValue($relationshipKey, $allRelContactDAO->refContact, $key, $row[$relationshipKey . '__' . $key]);
+              }
             }
-            return $fieldValue;
+          }
+          else {
+            $processor->setRelationshipValue($relationshipKey, $allRelContactDAO->refContact, $property, $row[$relationshipKey . '_' . $property]);
+          }
         }
       }
     }
-    elseif ($processor->isExportSpecifiedPaymentFields() && array_key_exists($field, self::componentPaymentFields())) {
-      $paymentTableId = $processor->getPaymentTableID();
-      $paymentData = CRM_Utils_Array::value($iterationDAO->$paymentTableId, $paymentDetails);
-      $payFieldMapper = array(
-        'componentPaymentField_total_amount' => 'total_amount',
-        'componentPaymentField_contribution_status' => 'contribution_status',
-        'componentPaymentField_payment_instrument' => 'pay_instru',
-        'componentPaymentField_transaction_id' => 'trxn_id',
-        'componentPaymentField_received_date' => 'receive_date',
-      );
-      return CRM_Utils_Array::value($payFieldMapper[$field], $paymentData, '');
-    }
-    else {
-      // if field is empty or null
-      return '';
-    }
   }
 
 }