Merge pull request #14485 from seamuslee001/domain_uniqueness_cache_key
[civicrm-core.git] / CRM / Report / Form / Contribute / Detail.php
CommitLineData
6a488035 1<?php
6a488035
TO
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
6b83d5bd 31 * @copyright CiviCRM LLC (c) 2004-2019
6a488035
TO
32 */
33class CRM_Report_Form_Contribute_Detail extends CRM_Report_Form {
6a488035
TO
34
35 protected $_summary = NULL;
36
e9b25936
E
37 protected $_softFrom = NULL;
38
f568f786 39 protected $noDisplayContributionOrSoftColumn = FALSE;
40
be2fb01f 41 protected $_customGroupExtends = [
70bea8e2 42 'Contact',
43 'Individual',
7d793900 44 'Contribution',
be2fb01f 45 ];
6a488035 46
81a22d3d 47 protected $groupConcatTested = TRUE;
48
e6bab5ea 49 protected $isTempTableBuilt = FALSE;
50
51 /**
52 * Query mode.
53 *
54 * This can be 'Main' or 'SoftCredit' to denote which query we are building.
55 *
56 * @var string
57 */
58 protected $queryMode = 'Main';
59
60 /**
61 * Is this report being run on contributions as the base entity.
62 *
63 * The report structure is generally designed around a base entity but
64 * depending on input it can be run in a sort of hybrid way that causes a lot
65 * of complexity.
66 *
67 * If it is in isContributionsOnlyMode we can simplify.
68 *
69 * (arguably there should be 2 separate report templates, not one doing double duty.)
70 *
71 * @var bool
72 */
73 protected $isContributionBaseMode = FALSE;
74
74cf4551 75 /**
1728e9a0 76 * This report has been optimised for group filtering.
77 *
78 * CRM-19170
79 *
80 * @var bool
81 */
82 protected $groupFilterNotOptimised = FALSE;
83
84 /**
85 * Class constructor.
74cf4551 86 */
00be9182 87 public function __construct() {
70bea8e2 88 $this->_autoIncludeIndexedFieldsAsOrderBys = 1;
be2fb01f
CW
89 $this->_columns = array_merge($this->getColumns('Contact', [
90 'order_bys_defaults' => ['sort_name' => 'ASC '],
91 'fields_defaults' => ['sort_name'],
92 'fields_excluded' => ['id'],
93 'fields_required' => ['id'],
94 'filters_defaults' => ['is_deleted' => 0],
d7e34fc6 95 'no_field_disambiguation' => TRUE,
be2fb01f
CW
96 ]), [
97 'civicrm_email' => [
a1a2a83d 98 'dao' => 'CRM_Core_DAO_Email',
be2fb01f
CW
99 'fields' => [
100 'email' => [
a1a2a83d
TO
101 'title' => ts('Donor Email'),
102 'default' => TRUE,
be2fb01f
CW
103 ],
104 ],
a1a2a83d 105 'grouping' => 'contact-fields',
be2fb01f
CW
106 ],
107 'civicrm_line_item' => [
4a39d437 108 'dao' => 'CRM_Price_DAO_LineItem',
be2fb01f
CW
109 ],
110 'civicrm_phone' => [
a1a2a83d 111 'dao' => 'CRM_Core_DAO_Phone',
be2fb01f
CW
112 'fields' => [
113 'phone' => [
a1a2a83d
TO
114 'title' => ts('Donor Phone'),
115 'default' => TRUE,
116 'no_repeat' => TRUE,
be2fb01f
CW
117 ],
118 ],
a1a2a83d 119 'grouping' => 'contact-fields',
be2fb01f
CW
120 ],
121 'civicrm_contribution' => [
a1a2a83d 122 'dao' => 'CRM_Contribute_DAO_Contribution',
be2fb01f
CW
123 'fields' => [
124 'contribution_id' => [
a1a2a83d
TO
125 'name' => 'id',
126 'no_display' => TRUE,
127 'required' => TRUE,
be2fb01f
CW
128 ],
129 'list_contri_id' => [
a1a2a83d
TO
130 'name' => 'id',
131 'title' => ts('Contribution ID'),
be2fb01f
CW
132 ],
133 'financial_type_id' => [
a1a2a83d
TO
134 'title' => ts('Financial Type'),
135 'default' => TRUE,
be2fb01f
CW
136 ],
137 'contribution_status_id' => [
a1a2a83d 138 'title' => ts('Contribution Status'),
be2fb01f
CW
139 ],
140 'contribution_page_id' => [
a1a2a83d 141 'title' => ts('Contribution Page'),
be2fb01f
CW
142 ],
143 'source' => [
a1a2a83d 144 'title' => ts('Source'),
be2fb01f
CW
145 ],
146 'payment_instrument_id' => [
a1a2a83d 147 'title' => ts('Payment Type'),
be2fb01f
CW
148 ],
149 'check_number' => [
a1a2a83d 150 'title' => ts('Check Number'),
be2fb01f
CW
151 ],
152 'currency' => [
a1a2a83d
TO
153 'required' => TRUE,
154 'no_display' => TRUE,
be2fb01f 155 ],
a1a2a83d 156 'trxn_id' => NULL,
be2fb01f 157 'receive_date' => ['default' => TRUE],
a1a2a83d 158 'receipt_date' => NULL,
f53a072c 159 'thankyou_date' => NULL,
be2fb01f 160 'total_amount' => [
a1a2a83d
TO
161 'title' => ts('Amount'),
162 'required' => TRUE,
be2fb01f
CW
163 ],
164 'non_deductible_amount' => [
5afce5ad 165 'title' => ts('Non-deductible Amount'),
be2fb01f 166 ],
a1a2a83d
TO
167 'fee_amount' => NULL,
168 'net_amount' => NULL,
be2fb01f 169 'contribution_or_soft' => [
a1a2a83d
TO
170 'title' => ts('Contribution OR Soft Credit?'),
171 'dbAlias' => "'Contribution'",
be2fb01f
CW
172 ],
173 'soft_credits' => [
a1a2a83d
TO
174 'title' => ts('Soft Credits'),
175 'dbAlias' => "NULL",
be2fb01f
CW
176 ],
177 'soft_credit_for' => [
a1a2a83d
TO
178 'title' => ts('Soft Credit For'),
179 'dbAlias' => "NULL",
be2fb01f
CW
180 ],
181 'cancel_date' => [
441a5791 182 'title' => ts('Cancelled / Refunded Date'),
be2fb01f
CW
183 ],
184 'cancel_reason' => [
441a5791 185 'title' => ts('Cancellation / Refund Reason'),
be2fb01f
CW
186 ],
187 ],
188 'filters' => [
189 'contribution_or_soft' => [
a1a2a83d
TO
190 'title' => ts('Contribution OR Soft Credit?'),
191 'clause' => "(1)",
192 'operatorType' => CRM_Report_Form::OP_SELECT,
193 'type' => CRM_Utils_Type::T_STRING,
be2fb01f 194 'options' => [
a1a2a83d
TO
195 'contributions_only' => ts('Contributions Only'),
196 'soft_credits_only' => ts('Soft Credits Only'),
f072e08f 197 'both' => ts('Both'),
be2fb01f
CW
198 ],
199 ],
200 'receive_date' => ['operatorType' => CRM_Report_Form::OP_DATE],
201 'thankyou_date' => ['operatorType' => CRM_Report_Form::OP_DATE],
202 'contribution_source' => [
db96d968 203 'title' => ts('Source'),
204 'name' => 'source',
205 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
206 ],
207 'currency' => [
fd6a6828 208 'title' => ts('Currency'),
a1a2a83d
TO
209 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
210 'options' => CRM_Core_OptionGroup::values('currencies_enabled'),
211 'default' => NULL,
212 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
213 ],
214 'non_deductible_amount' => [
5afce5ad 215 'title' => ts('Non-deductible Amount'),
be2fb01f
CW
216 ],
217 'financial_type_id' => [
a1a2a83d
TO
218 'title' => ts('Financial Type'),
219 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
4f166b15 220 'options' => CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes(),
a1a2a83d 221 'type' => CRM_Utils_Type::T_INT,
be2fb01f
CW
222 ],
223 'contribution_page_id' => [
a1a2a83d
TO
224 'title' => ts('Contribution Page'),
225 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
226 'options' => CRM_Contribute_PseudoConstant::contributionPage(),
227 'type' => CRM_Utils_Type::T_INT,
be2fb01f
CW
228 ],
229 'payment_instrument_id' => [
a1a2a83d
TO
230 'title' => ts('Payment Type'),
231 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
232 'options' => CRM_Contribute_PseudoConstant::paymentInstrument(),
233 'type' => CRM_Utils_Type::T_INT,
be2fb01f
CW
234 ],
235 'contribution_status_id' => [
a1a2a83d
TO
236 'title' => ts('Contribution Status'),
237 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
238 'options' => CRM_Contribute_PseudoConstant::contributionStatus(),
be2fb01f 239 'default' => [1],
a1a2a83d 240 'type' => CRM_Utils_Type::T_INT,
be2fb01f
CW
241 ],
242 'total_amount' => ['title' => ts('Contribution Amount')],
243 'cancel_date' => [
441a5791 244 'title' => ts('Cancelled / Refunded Date'),
245 'operatorType' => CRM_Report_Form::OP_DATE,
be2fb01f
CW
246 ],
247 'cancel_reason' => [
441a5791 248 'title' => ts('Cancellation / Refund Reason'),
be2fb01f
CW
249 ],
250 ],
251 'order_bys' => [
252 'financial_type_id' => ['title' => ts('Financial Type')],
253 'contribution_status_id' => ['title' => ts('Contribution Status')],
254 'payment_instrument_id' => ['title' => ts('Payment Method')],
255 'receive_date' => ['title' => ts('Date Received')],
256 'thankyou_date' => ['title' => ts('Thank-you Date')],
257 ],
258 'group_bys' => [
259 'contribution_id' => [
81a22d3d 260 'name' => 'id',
261 'required' => TRUE,
d70ada18 262 'default' => TRUE,
81a22d3d 263 'title' => ts('Contribution'),
be2fb01f
CW
264 ],
265 ],
a1a2a83d 266 'grouping' => 'contri-fields',
be2fb01f
CW
267 ],
268 'civicrm_contribution_soft' => [
a1a2a83d 269 'dao' => 'CRM_Contribute_DAO_ContributionSoft',
be2fb01f
CW
270 'fields' => [
271 'soft_credit_type_id' => ['title' => ts('Soft Credit Type')],
d70ada18 272 'soft_credit_amount' => ['title' => ts('Soft Credit amount'), 'name' => 'amount', 'type' => CRM_Utils_Type::T_MONEY],
be2fb01f
CW
273 ],
274 'filters' => [
275 'soft_credit_type_id' => [
fd6a6828 276 'title' => ts('Soft Credit Type'),
a1a2a83d
TO
277 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
278 'options' => CRM_Core_OptionGroup::values('soft_credit_type'),
279 'default' => NULL,
280 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
281 ],
282 ],
283 'group_bys' => [
284 'soft_credit_id' => [
d70ada18 285 'name' => 'id',
286 'title' => ts('Soft Credit'),
be2fb01f
CW
287 ],
288 ],
289 ],
290 'civicrm_financial_trxn' => [
e5f5c94b 291 'dao' => 'CRM_Financial_DAO_FinancialTrxn',
be2fb01f
CW
292 'fields' => [
293 'card_type_id' => [
d72b084a 294 'title' => ts('Credit Card Type'),
be2fb01f
CW
295 ],
296 ],
297 'filters' => [
298 'card_type_id' => [
d72b084a 299 'title' => ts('Credit Card Type'),
e5f5c94b 300 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
5e0343e8 301 'options' => CRM_Financial_DAO_FinancialTrxn::buildOptions('card_type_id'),
e5f5c94b
E
302 'default' => NULL,
303 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
304 ],
305 ],
306 ],
307 'civicrm_batch' => [
433465bc
PN
308 'dao' => 'CRM_Batch_DAO_EntityBatch',
309 'grouping' => 'contri-fields',
be2fb01f
CW
310 'fields' => [
311 'batch_id' => [
49d516c6 312 'name' => 'batch_id',
433465bc 313 'title' => ts('Batch Name'),
be2fb01f
CW
314 ],
315 ],
316 'filters' => [
317 'bid' => [
433465bc
PN
318 'title' => ts('Batch Name'),
319 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
320 'options' => CRM_Batch_BAO_Batch::getBatches(),
321 'type' => CRM_Utils_Type::T_INT,
322 'dbAlias' => 'batch_civireport.batch_id',
be2fb01f
CW
323 ],
324 ],
325 ],
326 'civicrm_contribution_ordinality' => [
a1a2a83d
TO
327 'dao' => 'CRM_Contribute_DAO_Contribution',
328 'alias' => 'cordinality',
be2fb01f
CW
329 'filters' => [
330 'ordinality' => [
a1a2a83d
TO
331 'title' => ts('Contribution Ordinality'),
332 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
be2fb01f 333 'options' => [
a1a2a83d
TO
334 0 => 'First by Contributor',
335 1 => 'Second or Later by Contributor',
be2fb01f 336 ],
a1a2a83d 337 'type' => CRM_Utils_Type::T_INT,
be2fb01f
CW
338 ],
339 ],
340 ],
341 'civicrm_note' => [
a1a2a83d 342 'dao' => 'CRM_Core_DAO_Note',
be2fb01f
CW
343 'fields' => [
344 'contribution_note' => [
a1a2a83d
TO
345 'name' => 'note',
346 'title' => ts('Contribution Note'),
be2fb01f
CW
347 ],
348 ],
349 'filters' => [
350 'note' => [
a1a2a83d
TO
351 'name' => 'note',
352 'title' => ts('Contribution Note'),
353 'operator' => 'like',
354 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
355 ],
356 ],
357 ],
358 ]) + $this->addAddressFields(FALSE);
a703d90c 359 // The tests test for this variation of the sort_name field. Don't argue with the tests :-).
25cf1ac5 360 $this->_columns['civicrm_contact']['fields']['sort_name']['title'] = ts('Donor Name');
16e2e80c 361 $this->_groupFilter = TRUE;
6a488035 362 $this->_tagFilter = TRUE;
9db913d3 363 // If we have campaigns enabled, add those elements to both the fields, filters and sorting
364 $this->addCampaignFields('civicrm_contribution', FALSE, TRUE);
7a961f19 365
366 $this->_currencyColumn = 'civicrm_contribution_currency';
6a488035
TO
367 parent::__construct();
368 }
369
477be9cc
AH
370 /**
371 * Validate incompatible report settings.
372 *
373 * @return bool
374 * true if no error found
375 */
376 public function validate() {
377 // If you're displaying Contributions Only, you can't group by soft credit.
378 $contributionOrSoftVal = $this->getElementValue('contribution_or_soft_value');
379 if ($contributionOrSoftVal[0] == 'contributions_only') {
380 $groupBySoft = $this->getElementValue('group_bys');
381 if (CRM_Utils_Array::value('soft_credit_id', $groupBySoft)) {
382 $this->setElementError('group_bys', ts('You cannot group by soft credit when displaying contributions only. Please uncheck "Soft Credit" in the Grouping tab.'));
383 }
384 }
385
386 return parent::validate();
387 }
388
74cf4551 389 /**
8b7f2513 390 * Set the FROM clause for the report.
74cf4551 391 */
8b7f2513 392 public function from() {
97b4dc6b 393 $this->setFromBase('civicrm_contact');
394 $this->_from .= "
395 INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
396 ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_contribution']}.contact_id
397 AND {$this->_aliases['civicrm_contribution']}.is_test = 0";
40c655aa 398
d70ada18 399 $this->joinContributionToSoftCredit();
8b7f2513 400 $this->appendAdditionalFromJoins();
6a488035
TO
401 }
402
74cf4551
EM
403 /**
404 * @param $rows
405 *
406 * @return array
407 */
00be9182 408 public function statistics(&$rows) {
6a488035
TO
409 $statistics = parent::statistics($rows);
410
be2fb01f 411 $totalAmount = $average = $fees = $net = [];
7a961f19 412 $count = 0;
6a488035
TO
413 $select = "
414 SELECT COUNT({$this->_aliases['civicrm_contribution']}.total_amount ) as count,
415 SUM( {$this->_aliases['civicrm_contribution']}.total_amount ) as amount,
7a961f19 416 ROUND(AVG({$this->_aliases['civicrm_contribution']}.total_amount), 2) as avg,
7010bab9 417 {$this->_aliases['civicrm_contribution']}.currency as currency,
e468f7a2 418 SUM( {$this->_aliases['civicrm_contribution']}.fee_amount ) as fees,
7010bab9 419 SUM( {$this->_aliases['civicrm_contribution']}.net_amount ) as net
6a488035
TO
420 ";
421
7a961f19 422 $group = "\nGROUP BY {$this->_aliases['civicrm_contribution']}.currency";
423 $sql = "{$select} {$this->_from} {$this->_where} {$group}";
6a488035 424 $dao = CRM_Core_DAO::executeQuery($sql);
3b2bbbfb 425 $this->addToDeveloperTab($sql);
6a488035 426
7a961f19 427 while ($dao->fetch()) {
7010bab9
DG
428 $totalAmount[] = CRM_Utils_Money::format($dao->amount, $dao->currency) . " (" . $dao->count . ")";
429 $fees[] = CRM_Utils_Money::format($dao->fees, $dao->currency);
430 $net[] = CRM_Utils_Money::format($dao->net, $dao->currency);
9d72cede 431 $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
1c4d8c3e 432 $count += $dao->count;
6a488035 433 }
be2fb01f 434 $statistics['counts']['amount'] = [
be205937 435 'title' => ts('Total Amount (Contributions)'),
7a961f19 436 'value' => implode(', ', $totalAmount),
437 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
438 ];
439 $statistics['counts']['count'] = [
be205937 440 'title' => ts('Total Contributions'),
7a961f19 441 'value' => $count,
be2fb01f
CW
442 ];
443 $statistics['counts']['fees'] = [
7010bab9
DG
444 'title' => ts('Fees'),
445 'value' => implode(', ', $fees),
446 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
447 ];
448 $statistics['counts']['net'] = [
7010bab9
DG
449 'title' => ts('Net'),
450 'value' => implode(', ', $net),
451 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
452 ];
453 $statistics['counts']['avg'] = [
7a961f19 454 'title' => ts('Average'),
455 'value' => implode(', ', $average),
456 'type' => CRM_Utils_Type::T_STRING,
be2fb01f 457 ];
6a488035 458
70c41bce 459 // Stats for soft credits
9d72cede
EM
460 if ($this->_softFrom &&
461 CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) !=
462 'contributions_only'
463 ) {
be2fb01f 464 $totalAmount = $average = [];
9d72cede 465 $count = 0;
c548e07b 466 $select = "
70c41bce
DS
467SELECT COUNT(contribution_soft_civireport.amount ) as count,
468 SUM(contribution_soft_civireport.amount ) as amount,
469 ROUND(AVG(contribution_soft_civireport.amount), 2) as avg,
470 {$this->_aliases['civicrm_contribution']}.currency as currency";
c548e07b 471 $sql = "
f813f78e 472{$select}
473{$this->_softFrom}
c548e07b
DS
474GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
475 $dao = CRM_Core_DAO::executeQuery($sql);
3b2bbbfb 476 $this->addToDeveloperTab($sql);
c548e07b 477 while ($dao->fetch()) {
6c552737 478 $totalAmount[] = CRM_Utils_Money::format($dao->amount, $dao->currency) . " (" .
9d72cede
EM
479 $dao->count . ")";
480 $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
c548e07b
DS
481 $count += $dao->count;
482 }
be2fb01f 483 $statistics['counts']['softamount'] = [
c548e07b
DS
484 'title' => ts('Total Amount (Soft Credits)'),
485 'value' => implode(', ', $totalAmount),
486 'type' => CRM_Utils_Type::T_STRING,
be2fb01f
CW
487 ];
488 $statistics['counts']['softcount'] = [
c548e07b
DS
489 'title' => ts('Total Soft Credits'),
490 'value' => $count,
be2fb01f
CW
491 ];
492 $statistics['counts']['softavg'] = [
c548e07b
DS
493 'title' => ts('Average (Soft Credits)'),
494 'value' => implode(', ', $average),
495 'type' => CRM_Utils_Type::T_STRING,
be2fb01f 496 ];
70c41bce 497 }
70c41bce 498
6a488035
TO
499 return $statistics;
500 }
501
8b7f2513 502 /**
e6bab5ea 503 * Build the report query.
8b7f2513 504 *
e6bab5ea 505 * @param bool $applyLimit
8b7f2513 506 *
e6bab5ea 507 * @return string
8b7f2513 508 */
4a3c55d9 509 public function buildQuery($applyLimit = FALSE) {
e6bab5ea 510 if ($this->isTempTableBuilt) {
4a3c55d9 511 $this->limit();
fe42be71 512 return "SELECT SQL_CALC_FOUND_ROWS * FROM {$this->temporaryTables['civireport_contribution_detail_temp3']['name']} $this->_orderBy $this->_limit";
e6bab5ea 513 }
514 return parent::buildQuery($applyLimit);
515 }
70c41bce 516
e6bab5ea 517 /**
518 * Shared function for preliminary processing.
519 *
520 * This is called by the api / unit tests and the form layer and is
521 * the right place to do 'initial analysis of input'.
522 */
523 public function beginPostProcessCommon() {
524 // CRM-18312 - display soft_credits and soft_credits_for column
525 // when 'Contribution or Soft Credit?' column is not selected
526 if (empty($this->_params['fields']['contribution_or_soft'])) {
527 $this->_params['fields']['contribution_or_soft'] = 1;
528 $this->noDisplayContributionOrSoftColumn = TRUE;
529 }
51fa20cb 530
e6bab5ea 531 if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'contributions_only') {
532 $this->isContributionBaseMode = TRUE;
533 }
534 if ($this->isContributionBaseMode &&
535 (!empty($this->_params['fields']['soft_credit_type_id'])
536 || !empty($this->_params['soft_credit_type_id_value']))
537 ) {
538 unset($this->_params['fields']['soft_credit_type_id']);
539 if (!empty($this->_params['soft_credit_type_id_value'])) {
be2fb01f 540 $this->_params['soft_credit_type_id_value'] = [];
e6bab5ea 541 CRM_Core_Session::setStatus(ts('Is it not possible to filter on soft contribution type when not including soft credits.'));
542 }
543 }
70c41bce
DS
544 // 1. use main contribution query to build temp table 1
545 $sql = $this->buildQuery();
d70ada18 546 $this->createTemporaryTable('civireport_contribution_detail_temp1', $sql);
70c41bce 547
c548e07b 548 // 2. customize main contribution query for soft credit, and build temp table 2 with soft credit contributions only
e6bab5ea 549 $this->queryMode = 'SoftCredit';
550 // Rebuild select with no groupby. Do not let column headers change.
551 $headers = $this->_columnHeaders;
552 $this->select();
553 $this->_columnHeaders = $headers;
8b7f2513 554 $this->softCreditFrom();
be78fc2d
DL
555 // also include custom group from if included
556 // since this might be included in select
557 $this->customDataFrom();
558
70c41bce
DS
559 $select = str_ireplace('contribution_civireport.total_amount', 'contribution_soft_civireport.amount', $this->_select);
560 $select = str_ireplace("'Contribution' as", "'Soft Credit' as", $select);
d70ada18 561
e6bab5ea 562 // we inner join with temp1 to restrict soft contributions to those in temp1 table.
563 // no group by here as we want to display as many soft credit rows as actually exist.
d70ada18 564 $sql = "{$select} {$this->_from} {$this->_where} $this->_groupBy";
fe42be71 565 $this->createTemporaryTable('civireport_contribution_detail_temp2', $sql);
e6bab5ea 566
9d72cede
EM
567 if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
568 'soft_credits_only'
569 ) {
c548e07b
DS
570 // revise pager : prev, next based on soft-credits only
571 $this->setPager();
f813f78e 572 }
70c41bce
DS
573
574 // copy _from for later use of stats calculation for soft credits, and reset $this->_from to main query
575 $this->_softFrom = $this->_from;
c93f6d83
DL
576
577 // simple reset of ->_from
578 $this->from();
be78fc2d
DL
579
580 // also include custom group from if included
581 // since this might be included in select
c93f6d83 582 $this->customDataFrom();
70c41bce 583
c548e07b 584 // 3. Decide where to populate temp3 table from
e6bab5ea 585 if ($this->isContributionBaseMode
9d72cede 586 ) {
fe42be71 587 $this->createTemporaryTable('civireport_contribution_detail_temp3',
588 "(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']})"
e6bab5ea 589 );
9d72cede 590 }
4c9b6178 591 elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
9d72cede
EM
592 'soft_credits_only'
593 ) {
fe42be71 594 $this->createTemporaryTable('civireport_contribution_detail_temp3',
595 "(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp2']['name']})"
e6bab5ea 596 );
9d72cede
EM
597 }
598 else {
fe42be71 599 $this->createTemporaryTable('civireport_contribution_detail_temp3', "
d70ada18 600(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']})
c548e07b 601UNION ALL
fe42be71 602(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp2']['name']})");
c548e07b 603 }
e6bab5ea 604 $this->isTempTableBuilt = TRUE;
6a488035
TO
605 }
606
a9cf37f5 607 /**
e6bab5ea 608 * Store group bys into array - so we can check elsewhere what is grouped.
a9cf37f5 609 *
d70ada18 610 * If we are generating a table of soft credits we need to group by them.
a9cf37f5 611 */
e6bab5ea 612 protected function storeGroupByArray() {
613 if ($this->queryMode === 'SoftCredit') {
d70ada18 614 $this->_groupByArray = [$this->_aliases['civicrm_contribution_soft'] . '.id'];
a9cf37f5 615 }
e6bab5ea 616 else {
617 parent::storeGroupByArray();
a9cf37f5 618 }
619 }
620
74cf4551 621 /**
ced9bfed
EM
622 * Alter display of rows.
623 *
624 * Iterate through the rows retrieved via SQL and make changes for display purposes,
625 * such as rendering contacts as links.
626 *
627 * @param array $rows
628 * Rows generated by SQL, with an array for each row.
74cf4551 629 */
00be9182 630 public function alterDisplay(&$rows) {
9d72cede
EM
631 $entryFound = FALSE;
632 $display_flag = $prev_cid = $cid = 0;
633 $contributionTypes = CRM_Contribute_PseudoConstant::financialType();
6a488035
TO
634 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
635 $paymentInstruments = CRM_Contribute_PseudoConstant::paymentInstrument();
9d72cede 636 $contributionPages = CRM_Contribute_PseudoConstant::contributionPage();
433465bc 637 $batches = CRM_Batch_BAO_Batch::getBatches();
6a488035
TO
638 foreach ($rows as $rowNum => $row) {
639 if (!empty($this->_noRepeats) && $this->_outputMode != 'csv') {
640 // don't repeat contact details if its same as the previous row
641 if (array_key_exists('civicrm_contact_id', $row)) {
642 if ($cid = $row['civicrm_contact_id']) {
643 if ($rowNum == 0) {
644 $prev_cid = $cid;
645 }
646 else {
647 if ($prev_cid == $cid) {
648 $display_flag = 1;
649 $prev_cid = $cid;
650 }
651 else {
652 $display_flag = 0;
653 $prev_cid = $cid;
654 }
655 }
656
657 if ($display_flag) {
658 foreach ($row as $colName => $colVal) {
659 // Hide repeats in no-repeat columns, but not if the field's a section header
9d72cede
EM
660 if (in_array($colName, $this->_noRepeats) &&
661 !array_key_exists($colName, $this->_sections)
662 ) {
6a488035
TO
663 unset($rows[$rowNum][$colName]);
664 }
665 }
666 }
667 $entryFound = TRUE;
668 }
669 }
670 }
671
9d72cede
EM
672 if (CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) ==
673 'Contribution'
674 ) {
8e3ad5e0
WA
675 unset($rows[$rowNum]['civicrm_contribution_soft_soft_credit_type_id']);
676 }
6a488035 677
a703d90c 678 $entryFound = $this->alterDisplayContactFields($row, $rows, $rowNum, 'contribution/detail', ts('View Contribution Details')) ? TRUE : $entryFound;
6a488035 679 // convert donor sort name to link
9d72cede
EM
680 if (array_key_exists('civicrm_contact_sort_name', $row) &&
681 !empty($rows[$rowNum]['civicrm_contact_sort_name']) &&
6a488035
TO
682 array_key_exists('civicrm_contact_id', $row)
683 ) {
684 $url = CRM_Utils_System::url("civicrm/contact/view",
685 'reset=1&cid=' . $row['civicrm_contact_id'],
686 $this->_absoluteUrl
687 );
688 $rows[$rowNum]['civicrm_contact_sort_name_link'] = $url;
689 $rows[$rowNum]['civicrm_contact_sort_name_hover'] = ts("View Contact Summary for this Contact.");
690 }
691
6a488035
TO
692 if ($value = CRM_Utils_Array::value('civicrm_contribution_financial_type_id', $row)) {
693 $rows[$rowNum]['civicrm_contribution_financial_type_id'] = $contributionTypes[$value];
694 $entryFound = TRUE;
695 }
696 if ($value = CRM_Utils_Array::value('civicrm_contribution_contribution_status_id', $row)) {
697 $rows[$rowNum]['civicrm_contribution_contribution_status_id'] = $contributionStatus[$value];
698 $entryFound = TRUE;
699 }
0d0e945a
DG
700 if ($value = CRM_Utils_Array::value('civicrm_contribution_contribution_page_id', $row)) {
701 $rows[$rowNum]['civicrm_contribution_contribution_page_id'] = $contributionPages[$value];
702 $entryFound = TRUE;
703 }
6a488035
TO
704 if ($value = CRM_Utils_Array::value('civicrm_contribution_payment_instrument_id', $row)) {
705 $rows[$rowNum]['civicrm_contribution_payment_instrument_id'] = $paymentInstruments[$value];
706 $entryFound = TRUE;
707 }
433465bc
PN
708 if (!empty($row['civicrm_batch_batch_id'])) {
709 $rows[$rowNum]['civicrm_batch_batch_id'] = CRM_Utils_Array::value($row['civicrm_batch_batch_id'], $batches);
6a488035
TO
710 $entryFound = TRUE;
711 }
5e0343e8 712 if (!empty($row['civicrm_financial_trxn_card_type_id'])) {
713 $rows[$rowNum]['civicrm_financial_trxn_card_type_id'] = $this->getLabels($row['civicrm_financial_trxn_card_type_id'], 'CRM_Financial_DAO_FinancialTrxn', 'card_type_id');
e5f5c94b
E
714 $entryFound = TRUE;
715 }
6a488035
TO
716
717 // Contribution amount links to viewing contribution
cdc61ccc
AH
718 if ($value = CRM_Utils_Array::value('civicrm_contribution_total_amount', $row)) {
719 $rows[$rowNum]['civicrm_contribution_total_amount'] = CRM_Utils_Money::format($value, $row['civicrm_contribution_currency']);
720 if (CRM_Core_Permission::check('access CiviContribute')) {
721 $url = CRM_Utils_System::url(
722 "civicrm/contact/view/contribution",
723 [
724 'reset' => 1,
725 'id' => $row['civicrm_contribution_contribution_id'],
726 'cid' => $row['civicrm_contact_id'],
727 'action' => 'view',
728 'context' => 'contribution',
729 'selectedChild' => 'contribute',
730 ],
731 $this->_absoluteUrl
732 );
733 $rows[$rowNum]['civicrm_contribution_total_amount_link'] = $url;
734 $rows[$rowNum]['civicrm_contribution_total_amount_hover'] = ts("View Details of this Contribution.");
735 }
6a488035
TO
736 $entryFound = TRUE;
737 }
738
739 // convert campaign_id to campaign title
740 if (array_key_exists('civicrm_contribution_campaign_id', $row)) {
741 if ($value = $row['civicrm_contribution_campaign_id']) {
9db913d3 742 $rows[$rowNum]['civicrm_contribution_campaign_id'] = $this->campaigns[$value];
6a488035
TO
743 $entryFound = TRUE;
744 }
745 }
746
70c41bce
DS
747 // soft credits
748 if (array_key_exists('civicrm_contribution_soft_credits', $row) &&
9d72cede
EM
749 'Contribution' ==
750 CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) &&
70c41bce
DS
751 array_key_exists('civicrm_contribution_contribution_id', $row)
752 ) {
753 $query = "
67aad585 754SELECT civicrm_contact_id, civicrm_contact_sort_name, civicrm_contribution_total_amount, civicrm_contribution_currency
fe42be71 755FROM {$this->temporaryTables['civireport_contribution_detail_temp2']['name']}
70c41bce 756WHERE civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
9d72cede 757 $dao = CRM_Core_DAO::executeQuery($query);
70c41bce 758 $string = '';
1ebef749 759 $separator = ($this->_outputMode !== 'csv') ? "<br/>" : ' ';
70c41bce 760 while ($dao->fetch()) {
9d72cede
EM
761 $url = CRM_Utils_System::url("civicrm/contact/view", 'reset=1&cid=' .
762 $dao->civicrm_contact_id);
763 $string = $string . ($string ? $separator : '') .
764 "<a href='{$url}'>{$dao->civicrm_contact_sort_name}</a> " .
67aad585 765 CRM_Utils_Money::format($dao->civicrm_contribution_total_amount, $dao->civicrm_contribution_currency);
70c41bce
DS
766 }
767 $rows[$rowNum]['civicrm_contribution_soft_credits'] = $string;
768 }
769
770 if (array_key_exists('civicrm_contribution_soft_credit_for', $row) &&
9d72cede
EM
771 'Soft Credit' ==
772 CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) &&
70c41bce
DS
773 array_key_exists('civicrm_contribution_contribution_id', $row)
774 ) {
775 $query = "
f813f78e 776SELECT civicrm_contact_id, civicrm_contact_sort_name
d70ada18 777FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']}
70c41bce 778WHERE civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
9d72cede 779 $dao = CRM_Core_DAO::executeQuery($query);
70c41bce
DS
780 $string = '';
781 while ($dao->fetch()) {
9d72cede
EM
782 $url = CRM_Utils_System::url("civicrm/contact/view", 'reset=1&cid=' .
783 $dao->civicrm_contact_id);
784 $string = $string .
785 "\n<a href='{$url}'>{$dao->civicrm_contact_sort_name}</a>";
70c41bce
DS
786 }
787 $rows[$rowNum]['civicrm_contribution_soft_credit_for'] = $string;
788 }
789
e468f7a2 790 // CRM-18312 - hide 'contribution_or_soft' column if unchecked.
791 if (!empty($this->noDisplayContributionOrSoftColumn)) {
792 unset($rows[$rowNum]['civicrm_contribution_contribution_or_soft']);
793 unset($this->_columnHeaders['civicrm_contribution_contribution_or_soft']);
794 }
795
51fa20cb 796 //convert soft_credit_type_id into label
797 if (array_key_exists('civicrm_contribution_soft_soft_credit_type_id', $rows[$rowNum])) {
7a4192f7 798 $rows[$rowNum]['civicrm_contribution_soft_soft_credit_type_id'] = CRM_Core_PseudoConstant::getLabel(
799 'CRM_Contribute_BAO_ContributionSoft',
800 'soft_credit_type_id',
801 $row['civicrm_contribution_soft_soft_credit_type_id']
802 );
51fa20cb 803 }
804
6a488035
TO
805 $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, 'contribute/detail', 'List all contribution(s) for this ') ? TRUE : $entryFound;
806
807 // skip looking further in rows, if first row itself doesn't
808 // have the column we need
809 if (!$entryFound) {
810 break;
811 }
812 $lastKey = $rowNum;
813 }
814 }
1c4d8c3e 815
00be9182 816 public function sectionTotals() {
6a488035
TO
817
818 // Reports using order_bys with sections must populate $this->_selectAliases in select() method.
819 if (empty($this->_selectAliases)) {
820 return;
821 }
822
823 if (!empty($this->_sections)) {
824 // build the query with no LIMIT clause
9d72cede 825 $select = str_ireplace('SELECT SQL_CALC_FOUND_ROWS ', 'SELECT ', $this->_select);
6a488035
TO
826 $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy}";
827
828 // pull section aliases out of $this->_sections
829 $sectionAliases = array_keys($this->_sections);
830
be2fb01f 831 $ifnulls = [];
6a488035
TO
832 foreach (array_merge($sectionAliases, $this->_selectAliases) as $alias) {
833 $ifnulls[] = "ifnull($alias, '') as $alias";
834 }
b708c08d 835 $this->_select = "SELECT " . implode(", ", $ifnulls);
36d2f4d5 836 $this->_select = CRM_Contact_BAO_Query::appendAnyValueToSelect($ifnulls, $sectionAliases);
6a488035
TO
837
838 /* Group (un-limited) report by all aliases and get counts. This might
e70a7fc0
TO
839 * be done more efficiently when the contents of $sql are known, ie. by
840 * overriding this method in the report class.
841 */
6a488035
TO
842
843 $addtotals = '';
844
67aad585 845 if (array_search("civicrm_contribution_total_amount", $this->_selectAliases) !==
9d72cede
EM
846 FALSE
847 ) {
67aad585 848 $addtotals = ", sum(civicrm_contribution_total_amount) as sumcontribs";
6a488035
TO
849 $showsumcontribs = TRUE;
850 }
851
b708c08d 852 $query = $this->_select .
fe42be71 853 "$addtotals, count(*) as ct from {$this->temporaryTables['civireport_contribution_detail_temp3']['name']} group by " .
9d72cede 854 implode(", ", $sectionAliases);
6a488035 855 // initialize array of total counts
be2fb01f 856 $sumcontribs = $totals = [];
6a488035 857 $dao = CRM_Core_DAO::executeQuery($query);
3b2bbbfb 858 $this->addToDeveloperTab($query);
6a488035
TO
859 while ($dao->fetch()) {
860
861 // let $this->_alterDisplay translate any integer ids to human-readable values.
862 $rows[0] = $dao->toArray();
863 $this->alterDisplay($rows);
864 $row = $rows[0];
865
866 // add totals for all permutations of section values
be2fb01f 867 $values = [];
6a488035
TO
868 $i = 1;
869 $aliasCount = count($sectionAliases);
870 foreach ($sectionAliases as $alias) {
871 $values[] = $row[$alias];
872 $key = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values);
873 if ($i == $aliasCount) {
874 // the last alias is the lowest-level section header; use count as-is
875 $totals[$key] = $dao->ct;
9d72cede
EM
876 if ($showsumcontribs) {
877 $sumcontribs[$key] = $dao->sumcontribs;
878 }
1c4d8c3e 879 }
6a488035
TO
880 else {
881 // other aliases are higher level; roll count into their total
84178120 882 $totals[$key] = (array_key_exists($key, $totals)) ? $totals[$key] + $dao->ct : $dao->ct;
1c4d8c3e 883 if ($showsumcontribs) {
84178120 884 $sumcontribs[$key] = array_key_exists($key, $sumcontribs) ? $sumcontribs[$key] + $dao->sumcontribs : $dao->sumcontribs;
6a488035
TO
885 }
886 }
887 }
888 }
889 if ($showsumcontribs) {
be2fb01f 890 $totalandsum = [];
0161a899
ML
891 // ts exception to avoid having ts("%1 %2: %3")
892 $title = '%1 contributions / soft-credits: %2';
893
9d72cede
EM
894 if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
895 'contributions_only'
896 ) {
0161a899 897 $title = '%1 contributions: %2';
9d72cede 898 }
4c9b6178 899 elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
9d72cede
EM
900 'soft_credits_only'
901 ) {
0161a899 902 $title = '%1 soft-credits: %2';
bec9ef32 903 }
6a488035 904 foreach ($totals as $key => $total) {
be2fb01f 905 $totalandsum[$key] = ts($title, [
1c4d8c3e 906 1 => $total,
21dfd5f5 907 2 => CRM_Utils_Money::format($sumcontribs[$key]),
be2fb01f 908 ]);
6a488035
TO
909 }
910 $this->assign('sectionTotals', $totalandsum);
911 }
912 else {
913 $this->assign('sectionTotals', $totals);
914 }
915 }
916 }
96025800 917
8b7f2513 918 /**
919 * Generate the from clause as it relates to the soft credits.
920 */
921 public function softCreditFrom() {
922
97b4dc6b 923 $this->_from = "
d70ada18 924 FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']} temp1_civireport
97b4dc6b 925 INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
926 ON temp1_civireport.civicrm_contribution_contribution_id = {$this->_aliases['civicrm_contribution']}.id
927 INNER JOIN civicrm_contribution_soft contribution_soft_civireport
928 ON contribution_soft_civireport.contribution_id = {$this->_aliases['civicrm_contribution']}.id
929 INNER JOIN civicrm_contact {$this->_aliases['civicrm_contact']}
930 ON {$this->_aliases['civicrm_contact']}.id = contribution_soft_civireport.contact_id
931 {$this->_aclFrom}
932 ";
8b7f2513 933
5e3dec81
JP
934 //Join temp table if report is filtered by group. This is specific to 'notin' operator and covered in unit test(ref dev/core#212)
935 if (!empty($this->_params['gid_op']) && $this->_params['gid_op'] == 'notin') {
936 $this->joinGroupTempTable('civicrm_contact', 'id', $this->_aliases['civicrm_contact']);
937 }
8b7f2513 938 $this->appendAdditionalFromJoins();
939 }
940
941 /**
942 * Append the joins that are required regardless of context.
943 */
944 public function appendAdditionalFromJoins() {
945 if (!empty($this->_params['ordinality_value'])) {
946 $this->_from .= "
947 INNER JOIN (SELECT c.id, IF(COUNT(oc.id) = 0, 0, 1) AS ordinality FROM civicrm_contribution c LEFT JOIN civicrm_contribution oc ON c.contact_id = oc.contact_id AND oc.receive_date < c.receive_date GROUP BY c.id) {$this->_aliases['civicrm_contribution_ordinality']}
948 ON {$this->_aliases['civicrm_contribution_ordinality']}.id = {$this->_aliases['civicrm_contribution']}.id";
949 }
3b2bbbfb 950 $this->joinPhoneFromContact();
951 $this->joinAddressFromContact();
952 $this->joinEmailFromContact();
8b7f2513 953
8b7f2513 954 // include contribution note
955 if (!empty($this->_params['fields']['contribution_note']) ||
956 !empty($this->_params['note_value'])
957 ) {
958 $this->_from .= "
959 LEFT JOIN civicrm_note {$this->_aliases['civicrm_note']}
960 ON ( {$this->_aliases['civicrm_note']}.entity_table = 'civicrm_contribution' AND
961 {$this->_aliases['civicrm_contribution']}.id = {$this->_aliases['civicrm_note']}.entity_id )";
962 }
963 //for contribution batches
964 if (!empty($this->_params['fields']['batch_id']) ||
965 !empty($this->_params['bid_value'])
966 ) {
967 $this->_from .= "
968 LEFT JOIN civicrm_entity_financial_trxn eft
969 ON eft.entity_id = {$this->_aliases['civicrm_contribution']}.id AND
970 eft.entity_table = 'civicrm_contribution'
971 LEFT JOIN civicrm_entity_batch {$this->_aliases['civicrm_batch']}
972 ON ({$this->_aliases['civicrm_batch']}.entity_id = eft.financial_trxn_id
973 AND {$this->_aliases['civicrm_batch']}.entity_table = 'civicrm_financial_trxn')";
974 }
e5f5c94b 975 // for credit card type
125bbb86 976 $this->addFinancialTrxnFromClause();
8b7f2513 977 }
978
d70ada18 979 /**
980 * Add join to the soft credit table.
981 */
982 protected function joinContributionToSoftCredit() {
76faaa00
AH
983 if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'contributions_only'
984 && !$this->isTableSelected('civicrm_contribution_soft')) {
d70ada18 985 return;
986 }
987 $joinType = ' LEFT ';
988 if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'soft_credits_only') {
989 $joinType = ' INNER ';
990 }
991 $this->_from .= "
992 $joinType JOIN civicrm_contribution_soft {$this->_aliases['civicrm_contribution_soft']}
993 ON {$this->_aliases['civicrm_contribution_soft']}.contribution_id = {$this->_aliases['civicrm_contribution']}.id
994 ";
995 }
996
6a488035 997}