Fix contribution detail report to work with FULL GROUP BY mode
authoreileen <emcnaughton@wikimedia.org>
Fri, 6 Apr 2018 02:26:31 +0000 (14:26 +1200)
committereileen <emcnaughton@wikimedia.org>
Fri, 6 Apr 2018 08:43:11 +0000 (20:43 +1200)
CRM/Report/Form.php
CRM/Report/Form/Contribute/Detail.php

index c8795f440780d6a3d445d7fd659b7fc824b85cd3..411122e90b66f13e1b58fbe42a095e8c9817978f 100644 (file)
@@ -203,6 +203,10 @@ class CRM_Report_Form extends CRM_Core_Form {
    */
   protected $addPaging = TRUE;
 
+  protected $isForceGroupBy = FALSE;
+
+  protected $groupConcatTested = FALSE;
+
   /**
    * An attribute for checkbox/radio form field layout
    *
@@ -462,6 +466,18 @@ class CRM_Report_Form extends CRM_Core_Form {
 
   protected $sql;
 
+  /**
+   * An instruction not to add a Group By.
+   *
+   * This is relevant where the group by might be otherwise added after the code that determines the group by array.
+   *
+   * e.g. where stat fields are being added but other settings cause it to not be desirable to add a group by
+   * such as in pivot charts when no row header is set
+   *
+   * @var bool
+   */
+  protected $noGroupBy = FALSE;
+
   /**
    * SQL being run in this report as an array.
    *
@@ -762,6 +778,10 @@ class CRM_Report_Form extends CRM_Core_Form {
               $this->_columns[$tableName][$fieldGrp][$fieldName]['name'] = $name;
             }
 
+            if (!isset($this->_columns[$tableName][$fieldGrp][$fieldName]['table_name'])) {
+              $this->_columns[$tableName][$fieldGrp][$fieldName]['table_name'] = $tableName;
+            }
+
             // set dbAlias = alias.name, unless already set
             if (!isset($this->_columns[$tableName][$fieldGrp][$fieldName]['dbAlias'])) {
               $this->_columns[$tableName][$fieldGrp][$fieldName]['dbAlias']
@@ -2319,7 +2339,14 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
               $select = $this->addStatisticsToSelect($field, $tableName, $fieldName, $select);
             }
             else {
-              $select = $this->addBasicFieldToSelect($tableName, $fieldName, $field, $select);
+
+              $selectClause = $this->getSelectClauseWithGroupConcatIfNotGroupedBy($tableName, $fieldName, $field);
+              if ($selectClause) {
+                $select[] = $selectClause;
+              }
+              else {
+                $select = $this->addBasicFieldToSelect($tableName, $fieldName, $field, $select);
+              }
             }
           }
         }
@@ -2420,6 +2447,14 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
    * @return bool
    */
   public function selectClause(&$tableName, $tableKey, &$fieldName, &$field) {
+    if (!empty($field['pseudofield'])) {
+      $alias = "{$tableName}_{$fieldName}";
+      $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = CRM_Utils_Array::value('title', $field);
+      $this->_columnHeaders["{$tableName}_{$fieldName}"]['type'] = CRM_Utils_Array::value('type', $field);
+      $this->_columnHeaders["{$tableName}_{$fieldName}"]['dbAlias'] = CRM_Utils_Array::value('dbAlias', $field);
+      $this->_selectAliases[] = $alias;
+      return ' 1 as  ' . $alias;
+    }
     return FALSE;
   }
 
@@ -2629,10 +2664,12 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
     $this->groupBy();
     $this->orderBy();
 
-    // order_by columns not selected for display need to be included in SELECT
-    $unselectedSectionColumns = $this->unselectedSectionColumns();
-    foreach ($unselectedSectionColumns as $alias => $section) {
-      $this->_select .= ", {$section['dbAlias']} as {$alias}";
+    foreach ($this->unselectedOrderByColumns() as $alias => $field) {
+      $clause = $this->getSelectClauseWithGroupConcatIfNotGroupedBy($field['table_name'], $field['name'], $field);
+      if (!$clause) {
+        $clause = "{$field['dbAlias']} as {$alias}";
+      }
+      $this->_select .= ", $clause ";
     }
 
     if ($applyLimit && empty($this->_params['charts'])) {
@@ -2712,7 +2749,16 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
 
         if (!empty($orderByField)) {
           $this->_orderByFields[$orderByField['tplField']] = $orderByField;
-          $orderBys[] = "{$orderByField['dbAlias']} {$orderBy['order']}";
+          if ($this->groupConcatTested) {
+            $orderBys[$orderByField['tplField']] = "{$orderByField['tplField']} {$orderBy['order']}";
+          }
+          else {
+            // Not sure when this is preferable to using tplField (which has
+            // definitely been tested to work in cases then this does not.
+            // in caution not switching unless report has been tested for
+            // group concat functionality.
+            $orderBys[$orderByField['tplField']] = "{$orderByField['dbAlias']} {$orderBy['order']}";
+          }
 
           // Record any section headers for assignment to the template
           if (!empty($orderBy['section'])) {
@@ -2728,6 +2774,15 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
     $this->assign('sections', $this->_sections);
   }
 
+  /**
+   * Determine unselected columns.
+   *
+   * @return array
+   */
+  public function unselectedOrderByColumns() {
+    return array_diff_key($this->_orderByFields, $this->getSelectColumns());
+  }
+
   /**
    * Determine unselected columns.
    *
@@ -5349,4 +5404,31 @@ LEFT JOIN civicrm_contact {$field['alias']} ON {$field['alias']}.id = {$this->_a
     return $defaults;
   }
 
+  /**
+   * Get the select clause for a field, wrapping in GROUP_CONCAT if appropriate.
+   *
+   * Full group by mode dictates that a field must either be in the group by function or
+   * wrapped in a aggregate function. Here we wrap the field in GROUP_CONCAT if it is not in the
+   * group concat.
+   *
+   * @param string $tableName
+   * @param string $fieldName
+   * @param string $field
+   * @return string
+   */
+  protected function getSelectClauseWithGroupConcatIfNotGroupedBy($tableName, &$fieldName, &$field) {
+    if ($this->groupConcatTested && (!empty($this->_groupByArray) || $this->isForceGroupBy)) {
+      if ((empty($field['statistics']) || in_array('GROUP_CONCAT', $field['statistics']))) {
+        $label = CRM_Utils_Array::value('title', $field);
+        $alias = "{$tableName}_{$fieldName}";
+        $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = $label;
+        $this->_selectAliases[] = $alias;
+        if (empty($this->_groupByArray[$tableName . '_' . $fieldName])) {
+          return "GROUP_CONCAT(DISTINCT {$field['dbAlias']}) as $alias";
+        }
+        return "({$field['dbAlias']}) as $alias";
+      }
+    }
+  }
+
 }
index b64e45f58a5369d6f2d22e569e24affe512c891e..1ad1cf6ed04cbadbd0960a11eedea088dc2d267e 100644 (file)
@@ -44,6 +44,8 @@ class CRM_Report_Form_Contribute_Detail extends CRM_Report_Form {
     'Contribution',
   );
 
+  protected $groupConcatTested = TRUE;
+
   /**
    * This report has been optimised for group filtering.
    *
@@ -221,6 +223,13 @@ class CRM_Report_Form_Contribute_Detail extends CRM_Report_Form {
           'payment_instrument_id' => array('title' => ts('Payment Method')),
           'receive_date' => array('title' => ts('Date Received')),
         ),
+        'group_bys' => array(
+          'contribution_id' => array(
+            'name' => 'id',
+            'required' => TRUE,
+            'title' => ts('Contribution'),
+          ),
+        ),
         'grouping' => 'contri-fields',
       ),
       'civicrm_contribution_soft' => array(
@@ -243,7 +252,6 @@ class CRM_Report_Form_Contribute_Detail extends CRM_Report_Form {
         'fields' => array(
           'card_type_id' => array(
             'title' => ts('Credit Card Type'),
-            'dbAlias' => 'GROUP_CONCAT(financial_trxn_civireport.card_type_id SEPARATOR ",")',
           ),
         ),
         'filters' => array(
@@ -332,30 +340,6 @@ class CRM_Report_Form_Contribute_Detail extends CRM_Report_Form {
     parent::__construct();
   }
 
-  public function preProcess() {
-    parent::preProcess();
-  }
-
-  public function select() {
-    $this->_columnHeaders = array();
-
-    parent::select();
-  }
-
-  public function orderBy() {
-    parent::orderBy();
-
-    // please note this will just add the order-by columns to select query, and not display in column-headers.
-    // This is a solution to not throw fatal errors when there is a column in order-by, not present in select/display columns.
-    foreach ($this->_orderByFields as $orderBy) {
-      if (!array_key_exists($orderBy['name'], $this->_params['fields']) &&
-        empty($orderBy['section']) && (strpos($this->_select, $orderBy['dbAlias']) === FALSE)
-      ) {
-        $this->_select .= ", {$orderBy['dbAlias']} as {$orderBy['tplField']}";
-      }
-    }
-  }
-
   /**
    * Set the FROM clause for the report.
    */
@@ -381,11 +365,6 @@ class CRM_Report_Form_Contribute_Detail extends CRM_Report_Form {
     $this->appendAdditionalFromJoins();
   }
 
-  public function groupBy() {
-    $groupBy = array("{$this->_aliases['civicrm_contact']}.id", "{$this->_aliases['civicrm_contribution']}.id");
-    $this->_groupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($this->_selectClauses, $groupBy);
-  }
-
   /**
    * @param $rows
    *
@@ -584,34 +563,9 @@ UNION ALL
     $this->addToDeveloperTab($sql);
     CRM_Core_DAO::executeQuery($sql);
 
-    // 5. Re-construct order-by to make sense for final query on temp3 table
-    $orderBy = '';
-    if (!empty($this->_orderByArray)) {
-      $aliases = array_flip($this->_aliases);
-      $orderClause = array();
-      foreach ($this->_orderByArray as $clause) {
-        list($alias, $rest) = explode('.', $clause);
-        // CRM-17280 -- In case, we are ordering by custom fields
-        // modify $rest to match the alias used for them in temp3 table
-        $grp = new CRM_Core_DAO_CustomGroup();
-        $grp->table_name = $aliases[$alias];
-        if ($grp->find()) {
-          list($fld, $order) = explode(' ', $rest);
-          foreach ($this->_columns[$aliases[$alias]]['fields'] as $fldName => $value) {
-            if ($value['name'] == $fld) {
-              $fld = $fldName;
-            }
-          }
-          $rest = "{$fld} {$order}";
-        }
-        $orderClause[] = $aliases[$alias] . "_" . $rest;
-      }
-      $orderBy = (!empty($orderClause)) ? "ORDER BY " . implode(', ', $orderClause) : '';
-    }
-
     // 6. show result set from temp table 3
     $rows = array();
-    $sql = "SELECT * FROM civireport_contribution_detail_temp3 {$orderBy}";
+    $sql = "SELECT * FROM civireport_contribution_detail_temp3 $this->_orderBy";
     $this->buildRows($sql, $rows);
 
     // format result set.