CRM-17837 Improve Lybunt report.
[civicrm-core.git] / CRM / Report / Form / Contribute / Lybunt.php
CommitLineData
6a488035 1<?php
6a488035
TO
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
e7112fa7 6 | Copyright CiviCRM LLC (c) 2004-2015 |
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
e7112fa7 31 * @copyright CiviCRM LLC (c) 2004-2015
6a488035
TO
32 */
33class CRM_Report_Form_Contribute_Lybunt extends CRM_Report_Form {
34
6a488035
TO
35 protected $_charts = array(
36 '' => 'Tabular',
37 'barChart' => 'Bar Chart',
38 'pieChart' => 'Pie Chart',
39 );
40
c160fde8 41 /**
42 * This is the report that links will lead to.
43 *
44 * It is a bit problematic to use contribute/detail for anything other than a single contact
45 * as the filtering from this report does not carry over to that report.
46 *
47 * @var array
48 */
6a488035
TO
49 public $_drilldownReport = array('contribute/detail' => 'Link to Detail Report');
50
51 protected $lifeTime_from = NULL;
430ae6dd 52 protected $lifeTime_where = NULL;
c160fde8 53 protected $_customGroupExtends = array(
54 'Contact',
55 'Individual',
56 'Household',
57 'Organization',
58 );
59
60 /**
61 * Table containing list of contact IDs.
62 *
63 * @var string
64 */
65 protected $contactTempTable = '';
66
67 /**
68 * Table containing list of contact IDs.
69 *
70 * @var string
71 */
72 protected $groupTempTable = '';
430ae6dd 73
74cf4551 74 /**
c160fde8 75 * Status clause to be added in to both contact based & contribution based queries.
76 *
77 * The rationale seems to be that we construct a list of contacts and then show the relevant contributions for them.
78 *
79 * Presumably the clause originally was only status but now type is included too.
80 *
81 * @var string
82 */
83 protected $_statusClause = '';
84
85 /**
86 * Class constructor.
74cf4551 87 */
00be9182 88 public function __construct() {
55f71fa7 89 $this->_rollup = 'WITH ROLLUP';
49e1ea9f 90 $this->_autoIncludeIndexedFieldsAsOrderBys = 1;
9d72cede 91 $yearsInPast = 10;
6a488035 92 $yearsInFuture = 1;
9d72cede
EM
93 $date = CRM_Core_SelectValues::date('custom', NULL, $yearsInPast, $yearsInFuture);
94 $count = $date['maxYear'];
6a488035
TO
95 while ($date['minYear'] <= $count) {
96 $optionYear[$date['minYear']] = $date['minYear'];
97 $date['minYear']++;
98 }
99
d62fab33
RN
100 // Check if CiviCampaign is a) enabled and b) has active campaigns
101 $config = CRM_Core_Config::singleton();
102 $campaignEnabled = in_array("CiviCampaign", $config->enableComponents);
103 if ($campaignEnabled) {
104 $getCampaigns = CRM_Campaign_BAO_Campaign::getPermissionedCampaigns(NULL, NULL, TRUE, FALSE, TRUE);
105 $this->activeCampaigns = $getCampaigns['campaigns'];
106 asort($this->activeCampaigns);
f813f78e 107 }
d62fab33 108
6a488035 109 $this->_columns = array(
a130e045
DG
110 'civicrm_contact' => array(
111 'dao' => 'CRM_Contact_DAO_Contact',
112 'grouping' => 'contact-field',
c160fde8 113 'fields' => $this->getBasicContactFields(),
49e1ea9f 114 'order_bys' => array(
115 'sort_name' => array(
116 'title' => ts('Last Name, First Name'),
a7d034b3 117 'default' => '0',
49e1ea9f 118 'default_order' => 'ASC',
119 ),
120 'first_name' => array(
121 'name' => 'first_name',
122 'title' => ts('First Name'),
123 ),
124 'gender_id' => array(
125 'name' => 'gender_id',
126 'title' => ts('Gender'),
127 ),
128 'birth_date' => array(
129 'name' => 'birth_date',
130 'title' => ts('Birth Date'),
131 ),
a130e045
DG
132 'contact_type' => array(
133 'title' => ts('Contact Type'),
134 ),
135 'contact_sub_type' => array(
136 'title' => ts('Contact Subtype'),
6a488035
TO
137 ),
138 ),
a130e045
DG
139 'filters' => array(
140 'sort_name' => array(
141 'title' => ts('Donor Name'),
142 'operator' => 'like',
6a488035 143 ),
49e1ea9f 144 'id' => array(
145 'title' => ts('Contact ID'),
146 'no_display' => TRUE,
147 ),
148 'gender_id' => array(
149 'title' => ts('Gender'),
150 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
151 'options' => CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id'),
152 ),
153 'birth_date' => array(
154 'title' => ts('Birth Date'),
155 'operatorType' => CRM_Report_Form::OP_DATE,
156 ),
157 'contact_type' => array(
158 'title' => ts('Contact Type'),
159 ),
160 'contact_sub_type' => array(
161 'title' => ts('Contact Subtype'),
162 ),
c160fde8 163 'is_deceased' => array(),
164 'do_not_phone' => array(),
165 'do_not_email' => array(),
166 'do_not_sms' => array(),
167 'do_not_mail' => array(),
168 'is_opt_out' => array(),
6a488035 169 ),
a130e045 170 ),
3191db62
E
171 'civicrm_line_item' => array(
172 'dao' => 'CRM_Price_DAO_LineItem',
173 ),
a130e045
DG
174 'civicrm_email' => array(
175 'dao' => 'CRM_Core_DAO_Email',
176 'grouping' => 'contact-field',
177 'fields' => array(
178 'email' => array(
179 'title' => ts('Email'),
180 'default' => TRUE,
6a488035
TO
181 ),
182 ),
a130e045
DG
183 ),
184 'civicrm_phone' => array(
185 'dao' => 'CRM_Core_DAO_Phone',
186 'grouping' => 'contact-field',
187 'fields' => array(
188 'phone' => array(
189 'title' => ts('Phone'),
190 'default' => TRUE,
191 ),
192 ),
193 ),
ae5ffbb7 194 );
c160fde8 195 $this->_columns += $this->addAddressFields(FALSE);
ae5ffbb7 196 $this->_columns += array(
a130e045
DG
197 'civicrm_contribution' => array(
198 'dao' => 'CRM_Contribute_DAO_Contribution',
199 'fields' => array(
200 'contact_id' => array(
201 'title' => ts('contactId'),
202 'no_display' => TRUE,
203 'required' => TRUE,
204 'no_repeat' => TRUE,
205 ),
a130e045
DG
206 'receive_date' => array(
207 'title' => ts('Year'),
208 'no_display' => TRUE,
209 'required' => TRUE,
210 'no_repeat' => TRUE,
6a488035 211 ),
c160fde8 212 'last_year_total_amount' => array(
213 'title' => ts('Last Year Total'),
214 'default' => TRUE,
215 'type' => CRM_Utils_Type::T_MONEY,
216 ),
217 'civicrm_life_time_total' => array(
218 'title' => ts('Lifetime Total'),
219 'default' => TRUE,
220 'type' => CRM_Utils_Type::T_MONEY,
221 'statistics' => array('sum' => ts('Lifetime total')),
222 ),
6a488035 223 ),
a130e045
DG
224 'filters' => array(
225 'yid' => array(
226 'name' => 'receive_date',
227 'title' => ts('This Year'),
228 'operatorType' => CRM_Report_Form::OP_SELECT,
229 'options' => $optionYear,
230 'default' => date('Y'),
55f71fa7 231 'type' => CRM_Utils_Type::T_INT,
a130e045
DG
232 ),
233 'financial_type_id' => array(
234 'title' => ts('Financial Type'),
235 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
3191db62 236 'options' => CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes(),
a130e045
DG
237 ),
238 'contribution_status_id' => array(
239 'title' => ts('Contribution Status'),
240 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
241 'options' => CRM_Contribute_PseudoConstant::contributionStatus(),
242 'default' => array('1'),
243 ),
244 ),
a7d034b3 245 'order_bys' => array(
c160fde8 246 'last_year_total_amount' => array(
247 'title' => ts('Total amount last year'),
248 'default' => '1',
a7d034b3 249 'default_weight' => '0',
250 'default_order' => 'DESC',
251 ),
252 ),
a130e045
DG
253 ),
254 );
6a488035 255
d62fab33
RN
256 // If we have a campaign, build out the relevant elements
257 if ($campaignEnabled && !empty($this->activeCampaigns)) {
258 $this->_columns['civicrm_contribution']['fields']['campaign_id'] = array(
259 'title' => ts('Campaign'),
260 'default' => 'false',
261 );
9d72cede
EM
262 $this->_columns['civicrm_contribution']['filters']['campaign_id'] = array(
263 'title' => ts('Campaign'),
d62fab33
RN
264 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
265 'options' => $this->activeCampaigns,
266 );
267 }
268
16e2e80c 269 $this->_groupFilter = TRUE;
6a488035
TO
270 $this->_tagFilter = TRUE;
271 parent::__construct();
272 }
273
c160fde8 274 /**
275 * Build select clause for a single field.
276 *
277 * @param string $tableName
278 * @param string $tableKey
279 * @param string $fieldName
280 * @param string $field
281 *
282 * @return string
283 */
284 public function selectClause(&$tableName, $tableKey, &$fieldName, &$field) {
285 if ($fieldName == 'last_year_total_amount') {
286 $this->_columnHeaders["{$tableName}_{$fieldName}"] = $field;
287 $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = $this->getLastYearColumnTitle();
288 $this->_statFields[$this->getLastYearColumnTitle()] = "{$tableName}_{$fieldName}";
289 return "SUM(IF(" . $this->whereClauseLastYear('contribution_civireport.receive_date') . ", contribution_civireport.total_amount, 0)) as {$tableName}_{$fieldName}";
290 }
291 if ($fieldName == 'civicrm_life_time_total') {
292 $this->_columnHeaders["{$tableName}_{$fieldName}"] = $field;
293 $this->_statFields[$field['title']] = "{$tableName}_{$fieldName}";
294 return "SUM({$this->_aliases[$tableName]}.total_amount) as {$tableName}_{$fieldName}";
295 }
296 if ($fieldName == 'receive_date') {
297 return self::fiscalYearOffset($field['dbAlias']) .
298 " as {$tableName}_{$fieldName} ";
299 }
300 return FALSE;
301 }
6a488035 302
c160fde8 303 /**
304 * Get the title for the last year column.
305 */
306 public function getLastYearColumnTitle() {
307 if ($this->getYearFilterType() == 'calendar') {
308 return ts('Total for ') . ($this->getCurrentYear() - 1);
309 }
310 return ts('Total for Fiscal Year ') . ($this->getCurrentYear() - 1) . '-' . ($this->getCurrentYear());
311 }
6a488035 312
c160fde8 313 /**
314 * Construct from clause.
315 *
316 * On the first run we are creating a table of contacts to include in the report.
317 *
318 * Once contactTempTable is populated we should avoid using any further filters that affect
319 * the contacts that should be visible.
320 */
321 public function from() {
322 if (!empty($this->contactTempTable)) {
323 $this->_from = "
324 FROM civicrm_contribution {$this->_aliases['civicrm_contribution']}
325 INNER JOIN $this->contactTempTable restricted_contacts
326 ON restricted_contacts.cid = {$this->_aliases['civicrm_contribution']}.contact_id
327 AND {$this->_aliases['civicrm_contribution']}.is_test = 0
328 INNER JOIN civicrm_contact {$this->_aliases['civicrm_contact']}
329 ON restricted_contacts.cid = {$this->_aliases['civicrm_contact']}.id";
330 if ($this->isTableSelected('civicrm_email')) {
331 $this->_from .= "
332 LEFT JOIN civicrm_email {$this->_aliases['civicrm_email']}
333 ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_email']}.contact_id
334 AND {$this->_aliases['civicrm_email']}.is_primary = 1";
335 }
336 if ($this->isTableSelected('civicrm_phone')) {
337 $this->_from .= "
338 LEFT JOIN civicrm_phone {$this->_aliases['civicrm_phone']}
339 ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_phone']}.contact_id
340 AND {$this->_aliases['civicrm_phone']}.is_primary = 1";
6a488035 341 }
c160fde8 342 $this->addAddressFromClause();
6a488035 343 }
c160fde8 344 else {
345 if ($this->groupTempTable) {
346 $this->_from .= "FROM $this->groupTempTable {$this->_aliases['civicrm_contact']}";
347 }
348 else {
349 $this->_from .= "FROM civicrm_contact {$this->_aliases['civicrm_contact']}";
350 }
6a488035 351
c160fde8 352 $this->_from .= " INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']} ";
353 if (!$this->groupTempTable) {
354 // The received_date index is better than the contribution_status_id index (fairly substantially).
355 // But if we have already pre-filtered down to a group of contacts then we want that to be the
356 // primary filter and the index hint will block that.
357 $this->_from .= "USE index (received_date)";
358 }
359 $this->_from .= " ON {$this->_aliases['civicrm_contribution']}.contact_id = {$this->_aliases['civicrm_contact']}.id
360 AND {$this->_aliases['civicrm_contribution']}.is_test = 0
361 AND " . $this->whereClauseLastYear("{$this->_aliases['civicrm_contribution']}.receive_date") . "
362 {$this->_aclFrom}
363 LEFT JOIN civicrm_contribution cont_exclude ON cont_exclude.contact_id = {$this->_aliases['civicrm_contact']}.id
364 AND " . $this->whereClauseThisYear('cont_exclude.receive_date');
365 $this->selectivelyAddLocationTablesJoinsToFilterQuery();
366 }
6a488035 367
c160fde8 368 }
6a488035 369
c160fde8 370 /**
371 * Generate where clause.
372 *
373 * We are overriding this primarily for 'before-after' handling of the receive_date placeholder field.
374 *
375 * We call this twice. The first time we are generating a temp table and we want to do an IS NULL on the
376 * join that draws in contributions from this year. The second time we are filtering elsewhere (contacts via
377 * the temp table & contributions via selective addition of contributions in the select function).
378 *
379 * If lifetime total is NOT selected we can add a further filter here to possibly improve performance
380 * but the benefit if unproven as yet.
381 * $clause = $this->whereClauseLastYear("{$this->_aliases['civicrm_contribution']}.receive_date");
382 *
383 * @param array $field Field specifications
384 * @param string $op Query operator (not an exact match to sql)
385 * @param mixed $value
386 * @param float $min
387 * @param float $max
388 *
389 * @return null|string
390 */
391 public function whereClause(&$field, $op, $value, $min, $max) {
392 if ($field['name'] == 'receive_date') {
393 $clause = 1;
394 if (empty($this->contactTempTable)) {
395 $this->_whereClauses[] = "cont_exclude.id IS NULL";
396 }
397 }
398 else {
399 $clause = parent::whereClause($field, $op, $value, $min, $max);
a27c90f2 400 }
c160fde8 401 if ($field['name'] == 'contribution_status_id' || $field['name'] == 'financial_type_id') {
402 $this->_statusClause .= " AND " . $clause;
a27c90f2 403 }
c160fde8 404 return $clause;
6a488035
TO
405 }
406
c160fde8 407 /**
408 * Build where clause for groups.
409 *
410 * This has been overridden in order to:
411 * 1) only build the group clause when filtering
412 * 2) render the id field as id rather than contact_id in
413 * order to allow us to join on hte created temp table as if it
414 * were the contact table.
415 *
416 * Further refactoring could break down the parent function so it can be selectively
417 * leveraged.
418 *
419 * @param string $field
420 * @param mixed $value
421 * @param string $op
422 *
423 * @return string
424 */
425 public function whereGroupClause($field, $value, $op) {
426 if (empty($this->contactTempTable)) {
427 $group = new CRM_Contact_DAO_Group();
428 $group->is_active = 1;
429 $group->find();
430 $smartGroups = array();
431 while ($group->fetch()) {
432 if (in_array($group->id, $this->_params['gid_value']) &&
433 $group->saved_search_id
434 ) {
435 $smartGroups[] = $group->id;
6a488035
TO
436 }
437 }
6a488035 438
c160fde8 439 CRM_Contact_BAO_GroupContactCache::check($smartGroups);
440
441 $smartGroupQuery = '';
442 if (!empty($smartGroups)) {
443 $smartGroups = implode(',', $smartGroups);
444 $smartGroupQuery = " UNION DISTINCT
445 SELECT DISTINCT smartgroup_contact.contact_id as id
446 FROM civicrm_group_contact_cache smartgroup_contact
447 WHERE smartgroup_contact.group_id IN ({$smartGroups}) ";
448 }
449
450 $sqlOp = $this->getSQLOperator($op);
451 if (!is_array($value)) {
452 $value = array($value);
453 }
454 $clause = "{$field['dbAlias']} IN (" . implode(', ', $value) . ")";
6a488035 455
c160fde8 456 $query = "SELECT DISTINCT {$this->_aliases['civicrm_group']}.contact_id as id
457 FROM civicrm_group_contact {$this->_aliases['civicrm_group']}
458 WHERE {$clause} AND {$this->_aliases['civicrm_group']}.status = 'Added'
459 {$smartGroupQuery} ";
460 $this->buildGroupTempTable($query);
6a488035 461 }
c160fde8 462 return "1";
463 }
464 /**
465 * Generate where clause for last calendar year or fiscal year.
466 *
467 * @todo must be possible to re-use relative dates stuff.
468 *
469 * @param string $fieldName
470 *
471 * @return string
472 */
473 public function whereClauseLastYear($fieldName) {
474 return "$fieldName BETWEEN '" . $this->getFirstDateOfPriorRange() . "' AND '" . $this->getLastDateOfPriorRange() . "'";
6a488035
TO
475 }
476
a7d034b3 477 /**
478 * Generate where clause for last calendar year or fiscal year.
479 *
480 * @todo must be possible to re-use relative dates stuff.
481 *
482 * @param string $fieldName
483 *
c160fde8 484 * @param int $current_year
a7d034b3 485 * @return null|string
486 */
c160fde8 487 public function whereClauseThisYear($fieldName, $current_year = NULL) {
488 return "$fieldName BETWEEN '" . $this->getFirstDateOfCurrentRange() . "' AND '" . $this->getLastDateOfCurrentRange() . "'";
489 }
490
491
492 /**
493 * Get the year value for the current year.
494 *
495 * @return string
496 */
497 public function getCurrentYear() {
498 return $this->_params['yid_value'];
499 }
500
501 /**
502 * Get the date time of the first date in the 'this year' range.
503 *
504 * @return string
505 */
506 public function getFirstDateOfCurrentRange() {
507 $current_year = $this->getCurrentYear();
508 if ($this->getYearFilterType() == 'calendar') {
509 return "{$current_year }-01-01";
a7d034b3 510 }
511 else {
512 $fiscalYear = CRM_Core_Config::singleton()->fiscalYearStart;
c160fde8 513 return "{$current_year}-{$fiscalYear['M']}-{$fiscalYear['d']}";
a7d034b3 514 }
a7d034b3 515 }
516
c160fde8 517 /**
518 * Get the year value for the current year.
519 *
520 * @return string
521 */
522 public function getYearFilterType() {
523 return CRM_Utils_Array::value('yid_op', $this->_params, 'calendar');
524 }
a7d034b3 525
c160fde8 526 /**
527 * Get the date time of the last date in the 'this year' range.
528 *
529 * @return string
530 */
531 public function getLastDateOfCurrentRange() {
532 return date('YmdHis', strtotime('+ 1 year - 1 second', strtotime($this->getFirstDateOfCurrentRange())));
533 }
a7d034b3 534
c160fde8 535 /**
536 * Get the date time of the first date in the 'last year' range.
537 *
538 * @return string
539 */
540 public function getFirstDateOfPriorRange() {
541 return date('YmdHis', strtotime('- 1 year', strtotime($this->getFirstDateOfCurrentRange())));
542 }
a7d034b3 543
c160fde8 544 /**
545 * Get the date time of the last date in the 'last year' range.
546 *
547 * @return string
548 */
549 public function getLastDateOfPriorRange() {
550 return date('YmdHis', strtotime('+ 1 year - 1 second', strtotime($this->getFirstDateOfPriorRange())));
551 }
552
553
554 public function groupBy() {
555 $this->_groupBy = "GROUP BY {$this->_aliases['civicrm_contribution']}.contact_id ";
6a488035
TO
556 $this->assign('chartSupported', TRUE);
557 }
558
74cf4551
EM
559 /**
560 * @param $rows
561 *
562 * @return array
563 */
00be9182 564 public function statistics(&$rows) {
c160fde8 565
6a488035 566 $statistics = parent::statistics($rows);
c160fde8 567 // The parent class does something odd where it adds an extra row to the count for the grand total.
568 // Perhaps that works on some other report? But here it just seems odd.
569 $this->countStat($statistics, count($rows));
6a488035 570 if (!empty($rows)) {
c160fde8 571 if (!empty($this->rollupRow) && !empty($this->rollupRow['civicrm_contribution_last_year_total_amount'])) {
572 $statistics['counts']['civicrm_contribution_last_year_total_amount'] = array(
573 'value' => $this->rollupRow['civicrm_contribution_last_year_total_amount'],
574 'title' => $this->getLastYearColumnTitle(),
575 'type' => CRM_Utils_Type::T_MONEY,
576 );
577
578 }
579 if (!empty($this->rollupRow) && !empty($this->rollupRow['civicrm_contribution_civicrm_life_time_total'])) {
580 $statistics['counts']['civicrm_contribution_civicrm_life_time_total'] = array(
581 'value' => $this->rollupRow['civicrm_contribution_civicrm_life_time_total'],
582 'title' => ts('Total LifeTime'),
6a488035
TO
583 'type' => CRM_Utils_Type::T_MONEY,
584 );
585 }
c160fde8 586 else {
587 $select = "SELECT SUM({$this->_aliases['civicrm_contribution']}.total_amount) as amount,
588 SUM(IF( " . $this->whereClauseLastYear('contribution_civireport.receive_date') . ", contribution_civireport.total_amount, 0)) as last_year
589 ";
590 $sql = "{$select} {$this->_from} {$this->_where}";
591 $dao = CRM_Core_DAO::executeQuery($sql);
592 if ($dao->fetch()) {
593 $statistics['counts']['amount'] = array(
594 'value' => $dao->amount,
595 'title' => ts('Total LifeTime'),
596 'type' => CRM_Utils_Type::T_MONEY,
597 );
598 $statistics['counts']['last_year'] = array(
599 'value' => $dao->last_year,
600 'title' => $this->getLastYearColumnTitle(),
601 'type' => CRM_Utils_Type::T_MONEY,
602 );
603 }
604 }
6a488035
TO
605 }
606
607 return $statistics;
608 }
609
c160fde8 610 /**
611 * This function is called by both the api (tests) and the UI.
612 */
613 public function beginPostProcessCommon() {
614 // Call this first so we can construct the WHERE temp table before we get into the FROM stuff.
615 $this->storeWhereHavingClauseArray();
a7d034b3 616 $this->buildQuery();
c160fde8 617 // @todo this acl has no test coverage and is very hard to test manually so could be fragile.
fe32835e 618 $this->getPermissionedFTQuery($this);
c160fde8 619 $this->resetFormSql();
6a488035 620
c160fde8 621 $this->contactTempTable = 'civicrm_report_temp_lybunt_c_' . date('Ymd_') . uniqid();
e463c072 622 $this->limit();
c160fde8 623 $getContacts = "
624 CREATE TEMPORARY TABLE $this->contactTempTable
625 SELECT SQL_CALC_FOUND_ROWS {$this->_aliases['civicrm_contact']}.id as cid {$this->_from} {$this->_where}
626 GROUP BY {$this->_aliases['civicrm_contact']}.id";
e32ac38a 627 $this->addToDeveloperTab($getContacts);
c160fde8 628 CRM_Core_DAO::executeQuery($getContacts);
e463c072 629 if (empty($this->_params['charts'])) {
6a488035
TO
630 $this->setPager();
631 }
632
c160fde8 633 // Reset where clauses to be regenerated in postProcess.
634 $this->_whereClauses = array();
635 }
a7d034b3 636
c160fde8 637 /**
638 * This function is called by both the api (tests) and the UI.
639 *
640 * @todo consider moving this to the parent class & reusing the filter then render logic.
641 *
642 * (this approach is taken to it's extreme in the extended reports extension with it's 'preconstrain'
643 * concept).
644 *
645 * @param string $clause
646 */
647 public function buildGroupTempTable($clause) {
648 if (empty($this->groupTempTable)) {
649 $this->groupTempTable = 'civicrm_report_temp_lybunt_g_' . date('Ymd_') . uniqid();
650 CRM_Core_DAO::executeQuery("
651 CREATE TEMPORARY TABLE $this->groupTempTable
652 $clause
653 ");
654 CRM_Core_DAO::executeQuery("ALTER TABLE $this->groupTempTable ADD INDEX i_id(id)");
655 }
656 }
6a488035 657
c160fde8 658 /**
659 * Build the report query.
660 *
661 * The issue we are hitting is that if we want to do group by & then ORDER BY we have to
662 * wrap the query in an outer query with the order by - otherwise the group by takes precedent.
663 * This is an issue when we want to group by contact but order by the maximum aggregate donation.
664 *
665 * @param bool $applyLimit
666 *
667 * @return string
668 */
669 public function buildQuery($applyLimit = TRUE) {
670 $this->select();
671 $this->from();
672 $this->customDataFrom(empty($this->contactTempTable));
673 $this->where();
674 $this->groupBy();
675 $this->orderBy();
676 $this->getPermissionedFTQuery($this);
677 $limitFilter = '';
678
679 // order_by columns not selected for display need to be included in SELECT
680 // This differs from parent in that we are getting those not in order by rather than not in
681 // sections, as we need to adapt to our contact group by.
682 $unselectedSectionColumns = array_diff_key($this->_orderByFields, $this->getSelectColumns());
683 foreach ($unselectedSectionColumns as $alias => $section) {
684 $this->_select .= ", {$section['dbAlias']} as {$alias}";
685 }
6a488035 686
c160fde8 687 if ($applyLimit && empty($this->_params['charts'])) {
688 $this->limit();
689 }
6a488035 690
c160fde8 691 $sql = "{$this->_select} {$this->_from} {$this->_where} $limitFilter {$this->_groupBy} {$this->_having} {$this->_rollup}";
6a488035 692
c160fde8 693 if (!empty($this->_orderByArray)) {
694 $this->_orderBy = str_replace('contact_civireport.', 'civicrm_contact_', "ORDER BY ISNULL(civicrm_contribution_contact_id), " . implode(', ', $this->_orderByArray));
695 $this->_orderBy = str_replace('contribution_civireport.', 'civicrm_contribution_', $this->_orderBy);
696 foreach ($this->_orderByFields as $field) {
697 $this->_orderBy = str_replace($field['dbAlias'], $field['tplField'], $this->_orderBy);
6a488035 698 }
c160fde8 699 $sql = str_replace('SQL_CALC_FOUND_ROWS', '', $sql);
700 $sql = "SELECT SQL_CALC_FOUND_ROWS * FROM ( $sql ) as inner_query {$this->_orderBy} $this->_limit";
6a488035
TO
701 }
702
c160fde8 703 CRM_Utils_Hook::alterReportVar('sql', $this, $this);
704 $this->addToDeveloperTab($sql);
6a488035 705
c160fde8 706 return $sql;
6a488035
TO
707 }
708
a7d034b3 709 /**
710 * Reset the form sql to prevent misleading developer tab info.
711 */
c160fde8 712 protected function resetFormSql() {
a7d034b3 713 $this->sql = '';
c160fde8 714 $this->sqlArray = array();
a7d034b3 715 }
716
717 /**
718 * Are we ordering by the latest year total.
719 *
720 * If we are we need to drop the rollup to do the ordering.
721 *
722 * Without bigger changes we can't get the lifetime total and order by
723 * the latest year total in the same query.
724 *
725 * @return bool
726 */
727 public function isOrderByLastYearTotal() {
728 $this->storeOrderByArray();
729 foreach ($this->_orderByArray as $orderBy) {
730 if (stristr($orderBy, 'contribution_civireport.total_amount')) {
731 return TRUE;
732 }
733 }
734 return FALSE;
735 }
736
74cf4551
EM
737 /**
738 * @param $rows
739 */
00be9182 740 public function buildChart(&$rows) {
6a488035
TO
741
742 $graphRows = array();
9d72cede
EM
743 $count = 0;
744 $display = array();
6a488035
TO
745
746 $current_year = $this->_params['yid_value'];
747 $previous_year = $current_year - 1;
748 $interval[$previous_year] = $previous_year;
749 $interval['life_time'] = 'Life Time';
750
751 foreach ($rows as $key => $row) {
9d72cede
EM
752 $display['life_time'] = CRM_Utils_Array::value('life_time', $display) +
753 $row['civicrm_life_time_total'];
ae5ffbb7 754 $display[$previous_year] = CRM_Utils_Array::value($previous_year, $display) + $row[$previous_year];
6a488035
TO
755 }
756
9d72cede 757 $config = CRM_Core_Config::Singleton();
6a488035 758 $graphRows['value'] = $display;
9d72cede
EM
759 $chartInfo = array(
760 'legend' => ts('Lybunt Report'),
6a488035
TO
761 'xname' => ts('Year'),
762 'yname' => ts('Amount (%1)', array(1 => $config->defaultCurrency)),
763 );
764 if ($this->_params['charts']) {
765 // build chart.
766 CRM_Utils_OpenFlashChart::reportChart($graphRows, $this->_params['charts'], $interval, $chartInfo);
767 $this->assign('chartType', $this->_params['charts']);
768 }
769 }
770
74cf4551 771 /**
ced9bfed
EM
772 * Alter display of rows.
773 *
774 * Iterate through the rows retrieved via SQL and make changes for display purposes,
775 * such as rendering contacts as links.
776 *
777 * @param array $rows
778 * Rows generated by SQL, with an array for each row.
74cf4551 779 */
00be9182 780 public function alterDisplay(&$rows) {
72a9af52
NG
781 $entryFound = FALSE;
782
6a488035
TO
783 foreach ($rows as $rowNum => $row) {
784 //Convert Display name into link
785 if (array_key_exists('civicrm_contact_sort_name', $row) &&
786 array_key_exists('civicrm_contribution_contact_id', $row)
787 ) {
788 $url = CRM_Report_Utils_Report::getNextUrl('contribute/detail',
9d72cede
EM
789 'reset=1&force=1&id_op=eq&id_value=' .
790 $row['civicrm_contribution_contact_id'],
6a488035
TO
791 $this->_absoluteUrl, $this->_id, $this->_drilldownReport
792 );
793 $rows[$rowNum]['civicrm_contact_sort_name_link'] = $url;
794 $rows[$rowNum]['civicrm_contact_sort_name_hover'] = ts("View Contribution Details for this Contact.");
72a9af52 795 $entryFound = TRUE;
6a488035 796 }
d62fab33
RN
797
798 // convert campaign_id to campaign title
799 if (array_key_exists('civicrm_contribution_campaign_id', $row)) {
800 if ($value = $row['civicrm_contribution_campaign_id']) {
801 $rows[$rowNum]['civicrm_contribution_campaign_id'] = $this->activeCampaigns[$value];
802 $entryFound = TRUE;
803 }
804 }
a27c90f2 805
c160fde8 806 $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, NULL, 'List all contribution(s)') ? TRUE : $entryFound;
807 $entryFound = $this->alterDisplayContactFields($row, $rows, $rowNum, NULL, 'List all contribution(s)') ? TRUE : $entryFound;
49e1ea9f 808 //handle gender
809 if (array_key_exists('civicrm_contact_gender_id', $row)) {
810 if ($value = $row['civicrm_contact_gender_id']) {
811 $gender = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id');
812 $rows[$rowNum]['civicrm_contact_gender_id'] = $gender[$value];
813 }
814 $entryFound = TRUE;
815 }
816
817 // display birthday in the configured custom format
818 if (array_key_exists('civicrm_contact_birth_date', $row)) {
819 $birthDate = $row['civicrm_contact_birth_date'];
820 if ($birthDate) {
821 $rows[$rowNum]['civicrm_contact_birth_date'] = CRM_Utils_Date::customFormat($birthDate, '%Y%m%d');
822 }
823 $entryFound = TRUE;
824 }
825
72a9af52
NG
826 // skip looking further in rows, if first row itself doesn't
827 // have the column we need
828 if (!$entryFound) {
829 break;
a27c90f2 830 }
6a488035
TO
831 }
832 }
833
74cf4551 834 /**
4f1f1f2a 835 * Override "This Year" $op options
74cf4551
EM
836 * @param string $type
837 * @param null $fieldName
838 *
839 * @return array
840 */
00be9182 841 public function getOperationPair($type = "string", $fieldName = NULL) {
6a488035 842 if ($fieldName == 'yid') {
9d72cede
EM
843 return array(
844 'calendar' => ts('Is Calendar Year'),
21dfd5f5 845 'fiscal' => ts('Fiscal Year Starting'),
9d72cede 846 );
6a488035
TO
847 }
848 return parent::getOperationPair($type, $fieldName);
849 }
96025800 850
6a488035 851}