3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 class CRM_Report_Form_Contribute_SoftCredit
extends CRM_Report_Form
{
19 protected $_emailField = FALSE;
20 protected $_emailFieldCredit = FALSE;
21 protected $_phoneField = FALSE;
22 protected $_phoneFieldCredit = FALSE;
24 protected $_customGroupExtends = [
30 public $_drilldownReport = ['contribute/detail' => 'Link to Detail Report'];
33 * This report has not been optimised for group filtering.
35 * The functionality for group filtering has been improved but not
36 * all reports have been adjusted to take care of it. This report has not
37 * and will run an inefficient query until fixed.
40 * @see https://issues.civicrm.org/jira/browse/CRM-19170
42 protected $groupFilterNotOptimised = TRUE;
46 public function __construct() {
47 $this->optimisedForOnlyFullGroupBy
= FALSE;
50 'civicrm_contact' => [
51 'dao' => 'CRM_Contact_DAO_Contact',
53 'display_name_creditor' => [
54 'title' => ts('Soft Credit Name'),
55 'name' => 'sort_name',
56 'alias' => 'contact_civireport',
61 'title' => ts('Soft Credit Id'),
63 'alias' => 'contact_civireport',
67 'display_name_constituent' => [
68 'title' => ts('Contributor Name'),
69 'name' => 'sort_name',
70 'alias' => 'constituentname',
74 'title' => ts('Const Id'),
76 'alias' => 'constituentname',
81 'title' => ts('First Name'),
84 'title' => ts('Middle Name'),
87 'title' => ts('Last Name'),
90 'title' => ts('Gender'),
93 'title' => ts('Birth Date'),
97 'dbAlias' => 'TIMESTAMPDIFF(YEAR, contact_civireport.birth_date, CURDATE())',
100 'title' => ts('Contact Type'),
102 'contact_sub_type' => [
103 'title' => ts('Contact Subtype'),
106 'grouping' => 'contact-fields',
109 'title' => ts('Last Name, First Name'),
111 'default_weight' => '0',
112 'default_order' => 'ASC',
115 'name' => 'first_name',
116 'title' => ts('First Name'),
119 'name' => 'gender_id',
120 'title' => ts('Gender'),
123 'name' => 'birth_date',
124 'title' => ts('Birth Date'),
127 'name' => 'age_at_event',
128 'title' => ts('Age at Event'),
131 'title' => ts('Contact Type'),
133 'contact_sub_type' => [
134 'title' => ts('Contact Subtype'),
139 'name' => 'sort_name',
140 'title' => ts('Soft Credit Name'),
143 'title' => ts('Gender'),
144 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
145 'options' => CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'gender_id'),
148 'title' => ts('Birth Date'),
149 'operatorType' => CRM_Report_Form
::OP_DATE
,
152 'title' => ts('Contact Type'),
154 'contact_sub_type' => [
155 'title' => ts('Contact Subtype'),
160 'dao' => 'CRM_Core_DAO_Email',
162 'email_creditor' => [
163 'title' => ts('Soft Credit Email'),
165 'alias' => 'emailcredit',
169 'email_constituent' => [
170 'title' => ts('Contributor\'s Email'),
172 'alias' => 'emailconst',
175 'grouping' => 'contact-fields',
178 'dao' => 'CRM_Core_DAO_Phone',
180 'phone_creditor' => [
181 'title' => ts('Soft Credit Phone'),
183 'alias' => 'pcredit',
186 'phone_constituent' => [
187 'title' => ts('Contributor\'s Phone'),
193 'grouping' => 'contact-fields',
195 'civicrm_financial_type' => [
196 'dao' => 'CRM_Financial_DAO_FinancialType',
197 'fields' => ['financial_type' => NULL],
201 'title' => ts('Financial Type'),
202 'type' => CRM_Utils_Type
::T_INT
,
203 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
204 'options' => CRM_Contribute_BAO_Contribution
::buildOptions('financial_type_id', 'search'),
207 'grouping' => 'softcredit-fields',
209 'civicrm_contribution' => [
210 'dao' => 'CRM_Contribute_DAO_Contribution',
212 'contribution_source' => NULL,
215 'no_display' => TRUE,
218 'grouping' => 'softcredit-fields',
220 'receive_date' => ['operatorType' => CRM_Report_Form
::OP_DATE
],
221 'receipt_date' => ['operatorType' => CRM_Report_Form
::OP_DATE
],
223 'title' => ts('Currency'),
224 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
225 'options' => CRM_Core_OptionGroup
::values('currencies_enabled'),
227 'type' => CRM_Utils_Type
::T_STRING
,
229 'contribution_status_id' => [
230 'title' => ts('Contribution Status'),
231 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
232 'options' => CRM_Contribute_BAO_Contribution
::buildOptions('contribution_status_id', 'search'),
237 'civicrm_contribution_soft' => [
238 'dao' => 'CRM_Contribute_DAO_ContributionSoft',
240 'contribution_id' => [
241 'title' => ts('Contribution ID'),
242 'no_display' => TRUE,
246 'title' => ts('Amount Statistics'),
249 'sum' => ts('Aggregate Amount'),
250 'count' => ts('Contributions'),
251 'avg' => ts('Average'),
256 'no_display' => TRUE,
258 'soft_credit_type_id' => ['title' => ts('Soft Credit Type')],
261 'soft_credit_type_id' => [
262 'title' => ts('Soft Credit Type'),
263 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
264 'options' => CRM_Core_OptionGroup
::values('soft_credit_type'),
266 'type' => CRM_Utils_Type
::T_STRING
,
269 'title' => ts('Soft Credit Amount'),
272 'grouping' => 'softcredit-fields',
274 'civicrm_financial_trxn' => [
275 'dao' => 'CRM_Financial_DAO_FinancialTrxn',
278 'title' => ts('Credit Card Type'),
279 'dbAlias' => 'GROUP_CONCAT(financial_trxn_civireport.card_type_id SEPARATOR ",")',
284 'title' => ts('Credit Card Type'),
285 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
286 'options' => CRM_Financial_DAO_FinancialTrxn
::buildOptions('card_type_id'),
288 'type' => CRM_Utils_Type
::T_STRING
,
294 // If we have a campaign, build out the relevant elements
295 $this->addCampaignFields('civicrm_contribution');
297 // Add charts support
300 'barChart' => ts('Bar Chart'),
301 'pieChart' => ts('Pie Chart'),
304 $this->_groupFilter
= TRUE;
305 $this->_tagFilter
= TRUE;
307 $this->_currencyColumn
= 'civicrm_contribution_currency';
308 parent
::__construct();
311 public function preProcess() {
312 parent
::preProcess();
315 public function select() {
317 $this->_columnHeaders
= [];
318 foreach ($this->_columns
as $tableName => $table) {
319 if (array_key_exists('fields', $table)) {
320 foreach ($table['fields'] as $fieldName => $field) {
321 if (!empty($field['required']) ||
322 !empty($this->_params
['fields'][$fieldName])
325 // include email column if set
326 if ($tableName == 'civicrm_email') {
327 $this->_emailField
= TRUE;
328 $this->_emailFieldCredit
= TRUE;
330 elseif ($tableName == 'civicrm_email_creditor') {
331 $this->_emailFieldCredit
= TRUE;
334 // include phone columns if set
335 if ($tableName == 'civicrm_phone') {
336 $this->_phoneField
= TRUE;
337 $this->_phoneFieldCredit
= TRUE;
339 elseif ($tableName == 'civicrm_phone_creditor') {
340 $this->_phoneFieldCredit
= TRUE;
343 // only include statistics columns if set
344 if (!empty($field['statistics'])) {
345 foreach ($field['statistics'] as $stat => $label) {
346 switch (strtolower($stat)) {
348 $select[] = "SUM({$field['dbAlias']}) as {$tableName}_{$fieldName}_{$stat}";
349 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
350 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
351 $this->_statFields
[] = "{$tableName}_{$fieldName}_{$stat}";
355 $select[] = "COUNT({$field['dbAlias']}) as {$tableName}_{$fieldName}_{$stat}";
356 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['type'] = CRM_Utils_Type
::T_INT
;
357 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
358 $this->_statFields
[] = "{$tableName}_{$fieldName}_{$stat}";
362 $select[] = "ROUND(AVG({$field['dbAlias']}),2) as {$tableName}_{$fieldName}_{$stat}";
363 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
364 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
365 $this->_statFields
[] = "{$tableName}_{$fieldName}_{$stat}";
371 $select[] = "{$field['dbAlias']} as {$tableName}_{$fieldName}";
372 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['type'] = $field['type'] ??
NULL;
373 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['title'] = $field['title'];
379 $this->selectClause
= $select;
381 $this->_select
= 'SELECT ' . implode(', ', $select) . ' ';
385 * @param array $fields
386 * @param array $files
387 * @param CRM_Core_Form $self
391 public static function formRule($fields, $files, $self) {
392 $errors = $grouping = [];
396 public function from() {
397 $alias_constituent = 'constituentname';
398 $alias_creditor = 'contact_civireport';
400 FROM civicrm_contribution {$this->_aliases['civicrm_contribution']}
401 INNER JOIN civicrm_contribution_soft {$this->_aliases['civicrm_contribution_soft']}
402 ON {$this->_aliases['civicrm_contribution_soft']}.contribution_id =
403 {$this->_aliases['civicrm_contribution']}.id
404 INNER JOIN civicrm_contact {$alias_constituent}
405 ON {$this->_aliases['civicrm_contribution']}.contact_id =
406 {$alias_constituent}.id
407 LEFT JOIN civicrm_financial_type {$this->_aliases['civicrm_financial_type']}
408 ON {$this->_aliases['civicrm_contribution']}.financial_type_id =
409 {$this->_aliases['civicrm_financial_type']}.id
410 LEFT JOIN civicrm_contact {$alias_creditor}
411 ON {$this->_aliases['civicrm_contribution_soft']}.contact_id =
415 // include Constituent email field if email column is to be included
416 if ($this->_emailField
) {
417 $alias = 'emailconst';
419 LEFT JOIN civicrm_email {$alias}
420 ON {$alias_constituent}.id =
421 {$alias}.contact_id AND
422 {$alias}.is_primary = 1\n";
425 // include Creditors email field if email column is to be included
426 if ($this->_emailFieldCredit
) {
427 $alias = 'emailcredit';
429 LEFT JOIN civicrm_email {$alias}
430 ON {$alias_creditor}.id =
431 {$alias}.contact_id AND
432 {$alias}.is_primary = 1\n";
435 // include Constituents phone field if email column is to be included
436 if ($this->_phoneField
) {
439 LEFT JOIN civicrm_phone {$alias}
440 ON {$alias_constituent}.id =
441 {$alias}.contact_id AND
442 {$alias}.is_primary = 1\n";
445 // include Creditors phone field if email column is to be included
446 if ($this->_phoneFieldCredit
) {
449 LEFT JOIN civicrm_phone pcredit
450 ON {$alias_creditor}.id =
451 {$alias}.contact_id AND
452 {$alias}.is_primary = 1\n";
454 // for credit card type
455 $this->addFinancialTrxnFromClause();
458 public function groupBy() {
459 $this->_rollup
= 'WITH ROLLUP';
460 $this->_select
= CRM_Contact_BAO_Query
::appendAnyValueToSelect($this->selectClause
, ["{$this->_aliases['civicrm_contribution_soft']}.contact_id", "constituentname.id"]);
462 GROUP BY {$this->_aliases['civicrm_contribution_soft']}.contact_id, constituentname.id {$this->_rollup}";
465 public function where() {
467 $this->_where
.= " AND {$this->_aliases['civicrm_contribution']}.is_test = 0 AND {$this->_aliases['civicrm_contribution']}.is_template = 0 ";
475 public function statistics(&$rows) {
476 $statistics = parent
::statistics($rows);
479 SELECT COUNT({$this->_aliases['civicrm_contribution_soft']}.amount ) as count,
480 SUM({$this->_aliases['civicrm_contribution_soft']}.amount ) as amount,
481 ROUND(AVG({$this->_aliases['civicrm_contribution_soft']}.amount), 2) as avg,
482 {$this->_aliases['civicrm_contribution']}.currency as currency
485 $sql = "{$select} {$this->_from} {$this->_where}
486 GROUP BY {$this->_aliases['civicrm_contribution']}.currency
489 $dao = CRM_Core_DAO
::executeQuery($sql);
491 $totalAmount = $average = [];
492 while ($dao->fetch()) {
493 $totalAmount[] = CRM_Utils_Money
::format($dao->amount
, $dao->currency
) . '(' .
495 $average[] = CRM_Utils_Money
::format($dao->avg
, $dao->currency
);
496 $count +
= $dao->count
;
498 $statistics['counts']['amount'] = [
499 'title' => ts('Total Amount'),
500 'value' => implode(', ', $totalAmount),
501 'type' => CRM_Utils_Type
::T_STRING
,
503 $statistics['counts']['count'] = [
504 'title' => ts('Total Contributions'),
507 $statistics['counts']['avg'] = [
508 'title' => ts('Average'),
509 'value' => implode(', ', $average),
510 'type' => CRM_Utils_Type
::T_STRING
,
516 public function postProcess() {
517 $this->beginPostProcess();
519 $this->buildACLClause(['constituentname', 'contact_civireport']);
520 $sql = $this->buildQuery();
522 $rows = $graphRows = [];
523 $this->buildRows($sql, $rows);
525 $this->formatDisplay($rows);
527 // assign variables to templates
528 $this->doTemplateAssignment($rows);
529 $this->endPostProcess($rows);
533 * Alter display of rows.
535 * Iterate through the rows retrieved via SQL and make changes for display purposes,
536 * such as rendering contacts as links.
539 * Rows generated by SQL, with an array for each row.
541 public function alterDisplay(&$rows) {
543 $dispname_flag = $phone_flag = $email_flag = 0;
544 $prev_email = $prev_dispname = $prev_phone = NULL;
546 foreach ($rows as $rowNum => $row) {
547 // Link constituent (contributor) to contribution detail
548 if (array_key_exists('civicrm_contact_display_name_constituent', $row) &&
549 array_key_exists('civicrm_contact_id_constituent', $row)
552 $url = CRM_Report_Utils_Report
::getNextUrl('contribute/detail',
553 'reset=1&force=1&id_op=eq&id_value=' .
554 $row['civicrm_contact_id_constituent'],
555 $this->_absoluteUrl
, $this->_id
, $this->_drilldownReport
557 $rows[$rowNum]['civicrm_contact_display_name_constituent_link'] = $url;
558 $rows[$rowNum]['civicrm_contact_display_name_constituent_hover'] = ts('List all direct contribution(s) from this contact.');
562 // convert soft credit contact name to link
563 if (array_key_exists('civicrm_contact_display_name_creditor', $row) &&
564 !empty($rows[$rowNum]['civicrm_contact_display_name_creditor']) &&
565 array_key_exists('civicrm_contact_id_creditor', $row)
567 $url = CRM_Utils_System
::url("civicrm/contact/view",
568 'reset=1&cid=' . $row['civicrm_contact_id_creditor'],
571 $rows[$rowNum]['civicrm_contact_display_name_creditor_link'] = $url;
572 $rows[$rowNum]['civicrm_contact_display_name_creditor_hover'] = ts("View contact summary");
575 // make subtotals look nicer
576 if (array_key_exists('civicrm_contact_id_constituent', $row) &&
577 !$row['civicrm_contact_id_constituent']
579 $this->fixSubTotalDisplay($rows[$rowNum], $this->_statFields
);
583 // convert campaign_id to campaign title
584 if (array_key_exists('civicrm_contribution_campaign_id', $row)) {
585 if ($value = $row['civicrm_contribution_campaign_id']) {
586 $rows[$rowNum]['civicrm_contribution_campaign_id'] = $this->campaigns
[$value];
591 //convert soft_credit_type_id into label
592 if (array_key_exists('civicrm_contribution_soft_soft_credit_type_id', $rows[$rowNum])) {
593 $rows[$rowNum]['civicrm_contribution_soft_soft_credit_type_id'] = CRM_Core_PseudoConstant
::getLabel(
594 'CRM_Contribute_BAO_ContributionSoft',
595 'soft_credit_type_id',
596 $row['civicrm_contribution_soft_soft_credit_type_id']
600 if (!empty($row['civicrm_financial_trxn_card_type_id']) && !in_array('Subtotal', $rows[$rowNum])) {
601 $rows[$rowNum]['civicrm_financial_trxn_card_type_id'] = $this->getLabels($row['civicrm_financial_trxn_card_type_id'], 'CRM_Financial_DAO_FinancialTrxn', 'card_type_id');
605 $entryFound = $this->alterDisplayContactFields($row, $rows, $rowNum, NULL, NULL) ?
TRUE : $entryFound;
607 // skip looking further in rows, if first row itself doesn't
608 // have the column we need
614 $this->removeDuplicates($rows);