diff --git a/CRM/Report/Form.php b/CRM/Report/Form.php index c8795f440780..411122e90b66 100644 --- a/CRM/Report/Form.php +++ b/CRM/Report/Form.php @@ -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 @@ public function preProcess() { $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 @@ public function select() { $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 @@ public function select() { * @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 @@ public function buildQuery($applyLimit = TRUE) { $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 @@ public function storeOrderByArray() { 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 @@ public function storeOrderByArray() { $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 @@ protected function getDefaultsFromOptions($options) { 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"; + } + } + } + } diff --git a/CRM/Report/Form/Contribute/Detail.php b/CRM/Report/Form/Contribute/Detail.php index b64e45f58a53..1ad1cf6ed04c 100644 --- a/CRM/Report/Form/Contribute/Detail.php +++ b/CRM/Report/Form/Contribute/Detail.php @@ -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 @@ public function __construct() { '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 @@ public function __construct() { '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 @@ public function __construct() { 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 @@ public function from() { $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 @@ public function postProcess() { $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.