Merge pull request #17587 from civicrm/5.27
[civicrm-core.git] / CRM / Report / Form / Membership / Summary.php
CommitLineData
6a488035 1<?php
6a488035
TO
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17class CRM_Report_Form_Membership_Summary extends CRM_Report_Form {
18
19 protected $_summary = NULL;
20
be2fb01f 21 protected $_charts = [
6a488035
TO
22 '' => 'Tabular',
23 'barChart' => 'Bar Chart',
24 'pieChart' => 'Pie Chart',
be2fb01f 25 ];
2f4c2f5d 26
74cf4551 27 /**
55d2c6f1 28 * Constructor function.
74cf4551 29 */
00be9182 30 public function __construct() {
6a488035 31 // UI for selecting columns to appear in the report list
55d2c6f1 32 // array containing the columns, group_bys and filters build and provided to Form
be2fb01f
CW
33 $this->_columns = [
34 'civicrm_contact' => [
6a488035 35 'dao' => 'CRM_Contact_DAO_Contact',
be2fb01f
CW
36 'fields' => [
37 'sort_name' => [
9d72cede 38 'title' => ts('Member Name'),
6a488035
TO
39 'no_repeat' => TRUE,
40 'required' => TRUE,
be2fb01f
CW
41 ],
42 'id' => [
6a488035
TO
43 'no_display' => TRUE,
44 'required' => TRUE,
be2fb01f
CW
45 ],
46 ],
47 'group_bys' => [
48 'id' => ['title' => ts('Contact ID')],
49 'display_name' => [
9d72cede 50 'title' => ts('Contact Name'),
be2fb01f
CW
51 ],
52 ],
6a488035 53 'grouping' => 'contact-fields',
be2fb01f
CW
54 ],
55 'civicrm_membership_type' => [
6a488035
TO
56 'dao' => 'CRM_Member_DAO_MembershipType',
57 'grouping' => 'member-fields',
be2fb01f
CW
58 'filters' => [
59 'gid' => [
6a488035
TO
60 'name' => 'id',
61 'title' => ts('Membership Types'),
62 'type' => CRM_Utils_Type::T_INT + CRM_Utils_Type::T_ENUM,
63 'options' => CRM_Member_PseudoConstant::membershipType(),
be2fb01f
CW
64 ],
65 ],
66 ],
67 'civicrm_membership' => [
6a488035
TO
68 'dao' => 'CRM_Member_DAO_Membership',
69 'grouping' => 'member-fields',
be2fb01f
CW
70 'fields' => [
71 'membership_type_id' => [
e300cf31 72 'title' => ts('Membership Type'),
6a488035 73 'required' => TRUE,
be2fb01f 74 ],
6a488035 75 'join_date' => NULL,
be2fb01f 76 'start_date' => [
9d72cede 77 'title' => ts('Current Cycle Start Date'),
be2fb01f
CW
78 ],
79 'end_date' => [
9d72cede 80 'title' => ts('Current Cycle End Date'),
be2fb01f
CW
81 ],
82 ],
83 'group_bys' => [
84 'membership_type_id' => ['title' => ts('Membership Type')],
85 ],
86 'filters' => [
b2c9a0e3 87 'membership_join_date' => ['type' => CRM_Utils_Type::T_DATE],
be2fb01f
CW
88 ],
89 ],
90 'civicrm_address' => [
6a488035 91 'dao' => 'CRM_Core_DAO_Address',
be2fb01f 92 'fields' => [
6a488035
TO
93 'street_address' => NULL,
94 'city' => NULL,
95 'postal_code' => NULL,
be2fb01f 96 'state_province_id' => [
9d72cede 97 'title' => ts('State/Province'),
be2fb01f
CW
98 ],
99 'country_id' => [
9d72cede 100 'title' => ts('Country'),
6a488035 101 'default' => TRUE,
be2fb01f
CW
102 ],
103 ],
6a488035 104 'grouping' => 'contact-fields',
be2fb01f
CW
105 ],
106 'civicrm_email' => [
6a488035 107 'dao' => 'CRM_Core_DAO_Email',
be2fb01f 108 'fields' => ['email' => NULL],
6a488035 109 'grouping' => 'contact-fields',
be2fb01f
CW
110 ],
111 'civicrm_contribution' => [
6a488035 112 'dao' => 'CRM_Contribute_DAO_Contribution',
be2fb01f
CW
113 'filters' => [
114 'total_amount' => [
9d72cede 115 'title' => ts('Contribution Amount'),
be2fb01f
CW
116 ],
117 ],
118 ],
119 ];
6a488035
TO
120 parent::__construct();
121 }
122
55d2c6f1
EM
123 /**
124 * Pre-process function.
125 */
00be9182 126 public function preProcess() {
6a488035
TO
127 $this->assign('reportTitle', ts('Membership Summary Report'));
128 parent::preProcess();
129 }
130
55d2c6f1
EM
131 /**
132 * Generate select clause.
133 */
00be9182 134 public function select() {
185c566a 135 // @todo remove this in favour of just using parent.
be2fb01f
CW
136 $select = [];
137 $this->_columnHeaders = [];
6a488035
TO
138 foreach ($this->_columns as $tableName => $table) {
139 if (array_key_exists('fields', $table)) {
140 foreach ($table['fields'] as $fieldName => $field) {
9d72cede
EM
141 if (!empty($field['required']) ||
142 !empty($this->_params['fields'][$fieldName])
143 ) {
6a488035
TO
144 $select[] = "{$field['dbAlias']} as {$tableName}_{$fieldName}";
145 $this->_columnHeaders["{$tableName}_{$fieldName}"]['type'] = $field['type'];
146 $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = $field['title'];
147 }
148 }
149 }
150 }
151 $this->_select = "SELECT " . implode(', ', $select) . " ";
152 }
153
74cf4551 154 /**
7d73813f 155 * Generate from clause.
74cf4551 156 */
00be9182 157 public function from() {
6a488035
TO
158 $this->_from = NULL;
159
160 $this->_from = "
161FROM civicrm_contact {$this->_aliases['civicrm_contact']}
2f4c2f5d 162INNER JOIN civicrm_membership {$this->_aliases['civicrm_membership']}
6a488035 163 ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_membership']}.contact_id
2f4c2f5d 164LEFT JOIN civicrm_membership_type {$this->_aliases['civicrm_membership_type']}
6a488035 165 ON {$this->_aliases['civicrm_membership']}.membership_type_id = {$this->_aliases['civicrm_membership_type']}.id
2f4c2f5d 166LEFT JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
6a488035
TO
167 ON {$this->_aliases['civicrm_membership']}.contact_id = {$this->_aliases['civicrm_contribution']}.contact_id
168";
185c566a 169 $this->joinAddressFromContact();
170 $this->joinEmailFromContact();
6a488035
TO
171 }
172
7d73813f 173 /**
174 * Generate where clause.
175 *
176 * @todo this looks like it duplicates the parent & could go.
177 */
00be9182 178 public function where() {
be2fb01f 179 $clauses = [];
6a488035
TO
180 foreach ($this->_columns as $tableName => $table) {
181 if (array_key_exists('filters', $table)) {
182 foreach ($table['filters'] as $fieldName => $field) {
183 $clause = NULL;
184 if ($field['type'] & CRM_Utils_Type::T_DATE) {
9c1bc317
CW
185 $relative = $this->_params["{$fieldName}_relative"] ?? NULL;
186 $from = $this->_params["{$fieldName}_from"] ?? NULL;
187 $to = $this->_params["{$fieldName}_to"] ?? NULL;
6a488035
TO
188
189 if ($relative || $from || $to) {
190 $clause = $this->dateClause($field['name'], $relative, $from, $to);
191 }
192 }
193 else {
9c1bc317 194 $op = $this->_params["{$fieldName}_op"] ?? NULL;
6a488035
TO
195 if ($op) {
196 $clause = $this->whereClause($field,
197 $op,
198 CRM_Utils_Array::value("{$fieldName}_value", $this->_params),
199 CRM_Utils_Array::value("{$fieldName}_min", $this->_params),
200 CRM_Utils_Array::value("{$fieldName}_max", $this->_params)
201 );
202 }
203 }
204
205 if (!empty($clause)) {
206 $clauses[] = $clause;
207 }
208 }
209 }
210 }
211
212 if (empty($clauses)) {
213 $this->_where = "WHERE ( 1 ) ";
214 }
215 else {
216 $this->_where = "WHERE " . implode(' AND ', $clauses);
217 }
218 }
219
74cf4551 220 /**
55d2c6f1
EM
221 * Generate statistics (bottom section of the report).
222 *
223 * @param array $rows
74cf4551
EM
224 *
225 * @return array
226 */
00be9182 227 public function statistics(&$rows) {
be2fb01f
CW
228 $statistics = [];
229 $statistics[] = [
9d72cede 230 'title' => ts('Row(s) Listed'),
6a488035 231 'value' => count($rows),
be2fb01f 232 ];
6a488035
TO
233 return $statistics;
234 }
235
7d73813f 236 /**
237 * Generate group by clause.
238 *
239 * @todo looks like a broken duplicate of the parent.
240 */
00be9182 241 public function groupBy() {
6a488035
TO
242 $this->_groupBy = "";
243 if (is_array($this->_params['group_bys']) &&
244 !empty($this->_params['group_bys'])
245 ) {
246 foreach ($this->_columns as $tableName => $table) {
247 if (array_key_exists('group_bys', $table)) {
248 foreach ($table['group_bys'] as $fieldName => $field) {
a7488080 249 if (!empty($this->_params['group_bys'][$fieldName])) {
6a488035
TO
250 $this->_groupBy[] = $field['dbAlias'];
251 }
252 }
253 }
254 }
255
256 if (!empty($this->_statFields) &&
257 (($append && count($this->_groupBy) <= 1) || (!$append))
258 ) {
259 $this->_rollup = " WITH ROLLUP";
260 }
9d72cede
EM
261 $this->_groupBy = "GROUP BY " . implode(', ', $this->_groupBy) .
262 " {$this->_rollup} ";
6a488035
TO
263 }
264 else {
265 $this->_groupBy = "GROUP BY contact.id";
266 }
267 }
268
7d73813f 269 /**
270 * PostProcess function.
271 */
00be9182 272 public function postProcess() {
6a488035
TO
273 $this->_params = $this->controller->exportValues($this->_name);
274 if (empty($this->_params) &&
275 $this->_force
276 ) {
277 $this->_params = $this->_formValues;
278 }
279 $this->_formValues = $this->_params;
280
281 $this->processReportMode();
282
283 $this->select();
284
285 $this->from();
286
287 $this->where();
288
289 $this->groupBy();
290
291 $sql = "{$this->_select} {$this->_from} {$this->_where} {$this->_groupBy}";
292
9d72cede 293 $dao = CRM_Core_DAO::executeQuery($sql);
be2fb01f 294 $rows = $graphRows = [];
6a488035
TO
295 $count = 0;
296 while ($dao->fetch()) {
be2fb01f 297 $row = [];
6a488035
TO
298 foreach ($this->_columnHeaders as $key => $value) {
299 $row[$key] = $dao->$key;
300 }
301
a7488080 302 if (!empty($this->_params['charts']) &&
6a488035
TO
303 $row['civicrm_contribution_receive_date_subtotal']
304 ) {
305 $graphRows['receive_date'][] = $row['civicrm_contribution_receive_date_start'];
306 $graphRows[$this->_interval][] = $row['civicrm_contribution_receive_date_interval'];
307 $graphRows['value'][] = $row['civicrm_contribution_total_amount_sum'];
308 $count++;
309 }
310
311 $rows[] = $row;
312 }
313 $this->formatDisplay($rows);
314
315 $this->assign_by_ref('columnHeaders', $this->_columnHeaders);
316 $this->assign_by_ref('rows', $rows);
317 $this->assign('statistics', $this->statistics($rows));
318
a7488080 319 if (!empty($this->_params['charts'])) {
be2fb01f 320 foreach ([
c86d4e7c
SL
321 'receive_date',
322 $this->_interval,
323 'value',
324 ] as $ignore) {
6a488035
TO
325 unset($graphRows[$ignore][$count - 1]);
326 }
327
328 // build chart.
dc61ee93 329 CRM_Utils_Chart::chart($graphRows, $this->_params['charts'], $this->_interval);
6a488035
TO
330 }
331 parent::endPostProcess();
332 }
333
74cf4551 334 /**
ced9bfed
EM
335 * Alter display of rows.
336 *
337 * Iterate through the rows retrieved via SQL and make changes for display purposes,
338 * such as rendering contacts as links.
55d2c6f1
EM
339 *
340 * @param array $rows
ced9bfed 341 * Rows generated by SQL, with an array for each row.
74cf4551 342 */
00be9182 343 public function alterDisplay(&$rows) {
6a488035 344 $entryFound = FALSE;
be2fb01f 345 $checkList = [];
6a488035
TO
346
347 foreach ($rows as $rowNum => $row) {
348
349 if (!empty($this->_noRepeats)) {
350 // not repeat contact display names if it matches with the one
351 // in previous row
352
353 $repeatFound = FALSE;
354 foreach ($row as $colName => $colVal) {
355 if (is_array($checkList[$colName]) &&
356 in_array($colVal, $checkList[$colName])
357 ) {
358 $rows[$rowNum][$colName] = "";
359 $repeatFound = TRUE;
360 }
361 if (in_array($colName, $this->_noRepeats)) {
362 $checkList[$colName][] = $colVal;
363 }
364 }
365 }
366
367 //handle the Membership Type Ids
368 if (array_key_exists('civicrm_membership_membership_type_id', $row)) {
369 if ($value = $row['civicrm_membership_membership_type_id']) {
370 $rows[$rowNum]['civicrm_membership_membership_type_id'] = CRM_Member_PseudoConstant::membershipType($value, FALSE);
371 }
372 $entryFound = TRUE;
373 }
374
6a488035
TO
375 // convert display name to links
376 if (array_key_exists('civicrm_contact_sort_name', $row) &&
377 array_key_exists('civicrm_contact_id', $row)
378 ) {
55d2c6f1
EM
379 $url = CRM_Utils_System::url(
380 'civicrm/report/member/detail',
6a488035
TO
381 'reset=1&force=1&id_op=eq&id_value=' . $row['civicrm_contact_id'],
382 $this->_absoluteUrl
383 );
55d2c6f1
EM
384 $rows[$rowNum]['civicrm_contact_sort_name']
385 = "<a href='$url'>" . $row["civicrm_contact_sort_name"] . '</a>';
6a488035
TO
386 $entryFound = TRUE;
387 }
388
f9ec8c77 389 $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, NULL, NULL) ? TRUE : $entryFound;
6a488035
TO
390 // skip looking further in rows, if first row itself doesn't
391 // have the column we need
392 if (!$entryFound) {
393 break;
394 }
395 }
396 }
96025800 397
6a488035 398}