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_Member_Detail
extends CRM_Report_Form
{
19 protected $_summary = NULL;
21 protected $_customGroupExtends = [
30 protected $_customGroupGroupBy = FALSE;
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.
39 * @see https://issues.civicrm.org/jira/browse/CRM-19170
41 protected $groupFilterNotOptimised = FALSE;
46 public function __construct() {
48 'civicrm_contact' => [
49 'dao' => 'CRM_Contact_DAO_Contact',
50 'fields' => $this->getBasicContactFields(),
53 'title' => ts('Contact Name'),
57 'title' => ts('Is Deleted'),
59 'type' => CRM_Utils_Type
::T_BOOLEAN
,
61 'id' => ['no_display' => TRUE],
65 'title' => ts('Last Name, First Name'),
67 'default_weight' => '0',
68 'default_order' => 'ASC',
71 'grouping' => 'contact-fields',
73 'civicrm_membership' => [
74 'dao' => 'CRM_Member_DAO_Membership',
76 'membership_type_id' => [
77 'title' => ts('Membership Type'),
81 'membership_start_date' => [
82 'title' => ts('Start Date'),
85 'membership_end_date' => [
86 'title' => ts('End Date'),
89 'owner_membership_id' => [
90 'title' => ts('Primary/Inherited?'),
94 'title' => ts('Join Date'),
97 'source' => ['title' => ts('Source')],
100 'membership_join_date' => ['operatorType' => CRM_Report_Form
::OP_DATE
],
101 'membership_start_date' => ['operatorType' => CRM_Report_Form
::OP_DATE
],
102 'membership_end_date' => ['operatorType' => CRM_Report_Form
::OP_DATE
],
103 'owner_membership_id' => [
104 'title' => ts('Primary Membership'),
105 'operatorType' => CRM_Report_Form
::OP_INT
,
108 'name' => 'membership_type_id',
109 'title' => ts('Membership Types'),
110 'type' => CRM_Utils_Type
::T_INT
,
111 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
112 'options' => CRM_Member_PseudoConstant
::membershipType(),
116 'membership_type_id' => [
117 'title' => ts('Membership Type'),
119 'default_weight' => '1',
120 'default_order' => 'ASC',
123 'grouping' => 'member-fields',
126 'title' => ts('Membership'),
131 'civicrm_membership_status' => [
132 'dao' => 'CRM_Member_DAO_MembershipStatus',
133 'alias' => 'mem_status',
136 'title' => ts('Status'),
143 'title' => ts('Status'),
144 'type' => CRM_Utils_Type
::T_INT
,
145 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
146 'options' => CRM_Member_PseudoConstant
::membershipStatus(NULL, NULL, 'label'),
149 'grouping' => 'member-fields',
152 'dao' => 'CRM_Core_DAO_Email',
153 'fields' => ['email' => NULL],
154 'grouping' => 'contact-fields',
157 'dao' => 'CRM_Core_DAO_Phone',
158 'fields' => ['phone' => NULL],
159 'grouping' => 'contact-fields',
161 'civicrm_contribution' => [
162 'dao' => 'CRM_Contribute_DAO_Contribution',
164 'contribution_id' => [
166 'no_display' => TRUE,
169 'financial_type_id' => ['title' => ts('Financial Type')],
170 'contribution_status_id' => ['title' => ts('Contribution Status')],
171 'payment_instrument_id' => ['title' => ts('Payment Type')],
174 'no_display' => TRUE,
177 'receive_date' => NULL,
178 'receipt_date' => NULL,
179 'fee_amount' => NULL,
180 'net_amount' => NULL,
182 'title' => ts('Payment Amount (most recent)'),
183 'statistics' => ['sum' => ts('Amount')],
187 'receive_date' => ['operatorType' => CRM_Report_Form
::OP_DATE
],
188 'financial_type_id' => [
189 'title' => ts('Financial Type'),
190 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
191 'options' => CRM_Contribute_PseudoConstant
::financialType(),
192 'type' => CRM_Utils_Type
::T_INT
,
194 'payment_instrument_id' => [
195 'title' => ts('Payment Type'),
196 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
197 'options' => CRM_Contribute_PseudoConstant
::paymentInstrument(),
198 'type' => CRM_Utils_Type
::T_INT
,
201 'title' => ts('Currency'),
202 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
203 'options' => CRM_Core_OptionGroup
::values('currencies_enabled'),
205 'type' => CRM_Utils_Type
::T_STRING
,
207 'contribution_status_id' => [
208 'title' => ts('Contribution Status'),
209 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
210 'options' => CRM_Contribute_BAO_Contribution
::buildOptions('contribution_status_id', 'search'),
211 'type' => CRM_Utils_Type
::T_INT
,
213 'total_amount' => ['title' => ts('Contribution Amount')],
217 'title' => ts('Date Received'),
218 'default_weight' => '2',
219 'default_order' => 'DESC',
222 'grouping' => 'contri-fields',
224 'civicrm_contribution_recur' => [
225 'dao' => 'CRM_Contribute_DAO_ContributionRecur',
227 'autorenew_status_id' => [
228 'name' => 'contribution_status_id',
229 'title' => ts('Auto-Renew Subscription Status'),
233 'autorenew_status_id' => [
234 'name' => 'contribution_status_id',
235 'title' => ts('Auto-Renew Subscription Status?'),
236 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
237 'options' => [0 => ts('None'), -1 => ts('Ended')] + CRM_Contribute_BAO_ContributionRecur
::buildOptions('contribution_status_id', 'search'),
238 'type' => CRM_Utils_Type
::T_INT
,
241 'grouping' => 'member-fields',
243 ] +
$this->getAddressColumns([
244 // These options are only excluded because they were not previously present.
248 $this->_groupFilter
= TRUE;
249 $this->_tagFilter
= TRUE;
251 // If we have campaigns enabled, add those elements to both the fields, filters and sorting
252 $this->addCampaignFields('civicrm_membership', FALSE, TRUE);
254 $this->_currencyColumn
= 'civicrm_contribution_currency';
255 parent
::__construct();
258 public function preProcess() {
259 $this->assign('reportTitle', ts('Membership Detail Report'));
260 parent
::preProcess();
263 public function select() {
265 if (in_array('civicrm_contribution_recur_autorenew_status_id', $this->_selectAliases
)) {
266 // If we're getting auto-renew status we'll want to know if auto-renew has
268 $this->_selectClauses
[] = "{$this->_aliases['civicrm_contribution_recur']}.end_date as civicrm_contribution_recur_end_date";
269 $this->_selectAliases
[] = 'civicrm_contribution_recur_end_date';
270 // Regenerate SELECT part of query
271 $this->_select
= "SELECT " . implode(', ', $this->_selectClauses
) . " ";
272 $this->_columnHeaders
["civicrm_contribution_recur_end_date"] = [
275 'no_display' => TRUE,
280 public function from() {
281 $this->setFromBase('civicrm_contact');
284 INNER JOIN civicrm_membership {$this->_aliases['civicrm_membership']}
285 ON {$this->_aliases['civicrm_contact']}.id =
286 {$this->_aliases['civicrm_membership']}.contact_id AND {$this->_aliases['civicrm_membership']}.is_test = 0
287 LEFT JOIN civicrm_membership_status {$this->_aliases['civicrm_membership_status']}
288 ON {$this->_aliases['civicrm_membership_status']}.id =
289 {$this->_aliases['civicrm_membership']}.status_id ";
291 $this->joinAddressFromContact();
292 $this->joinPhoneFromContact();
293 $this->joinEmailFromContact();
295 //used when contribution field is selected.
296 if ($this->isTableSelected('civicrm_contribution')) {
298 LEFT JOIN civicrm_membership_payment cmp
299 ON {$this->_aliases['civicrm_membership']}.id = cmp.membership_id
300 LEFT JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
301 ON cmp.contribution_id={$this->_aliases['civicrm_contribution']}.id\n";
303 if ($this->isTableSelected('civicrm_contribution_recur')) {
304 $this->_from
.= <<<HERESQL
305 LEFT JOIN civicrm_contribution_recur {$this->_aliases['civicrm_contribution_recur']}
306 ON {$this->_aliases['civicrm_membership']}.contribution_recur_id = {$this->_aliases['civicrm_contribution_recur']}.id
312 * Override to add handling for autorenew status.
314 public function whereClause(&$field, $op, $value, $min, $max) {
315 if ($field['dbAlias'] == "{$this->_aliases['civicrm_contribution_recur']}.contribution_status_id") {
319 if ($value !== NULL && is_array($value) && count($value) > 0) {
320 $regularOptions = implode(', ', array_diff($value, [0, -1]));
322 if (in_array(0, $value)) {
323 $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL";
325 // Ended: not null, end_date in past
326 if (in_array(-1, $value)) {
327 $clauseParts[] = <<<HERESQL
328 {$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL
329 AND {$this->_aliases['civicrm_contribution_recur']}.end_date < NOW()
332 // Normal statuses: IN()
333 if (!empty($regularOptions)) {
334 $clauseParts[] = "{$this->_aliases['civicrm_contribution_recur']}.contribution_status_id IN ($regularOptions)";
336 // Double parentheses b/c ORs should be treated as a group
337 return '((' . implode(') OR (', $clauseParts) . '))';
342 if ($value !== NULL && is_array($value) && count($value) > 0) {
343 $regularOptions = implode(', ', array_diff($value, [0, -1]));
345 if (in_array(0, $value)) {
346 $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL";
348 // Ended: null or end_date in future
349 if (in_array(-1, $value)) {
350 $clauseParts[] = <<<HERESQL
351 {$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL
352 OR {$this->_aliases['civicrm_contribution_recur']}.end_date >= NOW()
353 OR {$this->_aliases['civicrm_contribution_recur']}.end_date IS NULL
356 // Normal statuses: null or NOT IN()
357 if (!empty($regularOptions)) {
358 $clauseParts[] = <<<HERESQL
359 {$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL
360 OR {$this->_aliases['civicrm_contribution_recur']}.contribution_status_id NOT IN ($regularOptions)
363 return '(' . implode(') AND (', $clauseParts) . ')';
368 return "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL";
371 return "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL";
375 return parent
::whereClause($field, $op, $value, $min, $max);
379 public function getOperationPair($type = "string", $fieldName = NULL) {
380 //re-name IS NULL/IS NOT NULL for clarity
381 if ($fieldName == 'owner_membership_id') {
383 $result['nll'] = ts('Primary members only');
384 $result['nnll'] = ts('Non-primary members only');
385 $options = parent
::getOperationPair($type, $fieldName);
386 foreach ($options as $key => $label) {
387 if (!array_key_exists($key, $result)) {
388 $result[$key] = $label;
393 $result = parent
::getOperationPair($type, $fieldName);
399 * Alter display of rows.
401 * Iterate through the rows retrieved via SQL and make changes for display purposes,
402 * such as rendering contacts as links.
405 * Rows generated by SQL, with an array for each row.
407 public function alterDisplay(&$rows) {
411 $contributionTypes = CRM_Contribute_PseudoConstant
::financialType();
412 $contributionStatus = CRM_Contribute_PseudoConstant
::contributionStatus(NULL, 'label');
413 $paymentInstruments = CRM_Contribute_PseudoConstant
::paymentInstrument();
415 $repeatFound = FALSE;
416 foreach ($rows as $rowNum => $row) {
417 if ($repeatFound == FALSE ||
418 $repeatFound < $rowNum - 1
423 if (!empty($this->_noRepeats
) && $this->_outputMode
!= 'csv') {
424 // not repeat contact display names if it matches with the one
426 foreach ($row as $colName => $colVal) {
427 if (in_array($colName, $this->_noRepeats
) &&
430 if ($rows[$rowNum][$colName] == $rows[$rowNum - 1][$colName] ||
431 (!empty($checkList[$colName]) &&
432 in_array($colVal, $checkList[$colName]))
434 $rows[$rowNum][$colName] = "";
435 // CRM-15917: Don't blank the name if it's a different contact
436 if ($colName == 'civicrm_contact_exposed_id') {
437 $rows[$rowNum]['civicrm_contact_sort_name'] = "";
439 $repeatFound = $rowNum;
442 if (in_array($colName, $this->_noRepeats
)) {
443 $checkList[$colName][] = $colVal;
448 if (array_key_exists('civicrm_membership_membership_type_id', $row)) {
449 if ($value = $row['civicrm_membership_membership_type_id']) {
450 $rows[$rowNum]['civicrm_membership_membership_type_id'] = CRM_Member_PseudoConstant
::membershipType($value, FALSE);
455 if (array_key_exists('civicrm_contact_sort_name', $row) &&
456 $rows[$rowNum]['civicrm_contact_sort_name'] &&
457 array_key_exists('civicrm_contact_id', $row)
459 $url = CRM_Utils_System
::url("civicrm/contact/view",
460 'reset=1&cid=' . $row['civicrm_contact_id'],
463 $rows[$rowNum]['civicrm_contact_sort_name_link'] = $url;
464 $rows[$rowNum]['civicrm_contact_sort_name_hover'] = ts("View Contact Summary for this Contact.");
468 if ($value = CRM_Utils_Array
::value('civicrm_contribution_financial_type_id', $row)) {
469 $rows[$rowNum]['civicrm_contribution_financial_type_id'] = $contributionTypes[$value];
472 if ($value = CRM_Utils_Array
::value('civicrm_contribution_contribution_status_id', $row)) {
473 $rows[$rowNum]['civicrm_contribution_contribution_status_id'] = $contributionStatus[$value];
476 if ($value = CRM_Utils_Array
::value('civicrm_contribution_payment_instrument_id', $row)) {
477 $rows[$rowNum]['civicrm_contribution_payment_instrument_id'] = $paymentInstruments[$value];
480 if ($value = $row['civicrm_contribution_recur_autorenew_status_id'] ??
NULL) {
481 $rows[$rowNum]['civicrm_contribution_recur_autorenew_status_id'] = $contributionStatus[$value];
482 if (!empty($row['civicrm_contribution_recur_end_date'])
483 && strtotime($row['civicrm_contribution_recur_end_date']) < time()) {
484 $ended = ts('ended');
485 $rows[$rowNum]['civicrm_contribution_recur_autorenew_status_id'] .= " ($ended)";
490 if (array_key_exists('civicrm_membership_owner_membership_id', $row)) {
491 $value = $row['civicrm_membership_owner_membership_id'];
492 $rows[$rowNum]['civicrm_membership_owner_membership_id'] = ($value != '') ?
'Inherited' : 'Primary';
496 // Convert campaign_id to campaign title
497 if (array_key_exists('civicrm_membership_campaign_id', $row)) {
498 if ($value = $row['civicrm_membership_campaign_id']) {
499 $rows[$rowNum]['civicrm_membership_campaign_id'] = $this->campaigns
[$value];
503 $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, 'member/detail', 'List all memberships(s) for this ') ?
TRUE : $entryFound;
504 $entryFound = $this->alterDisplayContactFields($row, $rows, $rowNum, 'member/detail', 'List all memberships(s) for this ') ?
TRUE : $entryFound;