3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2015
35 class CRM_Report_Form_Member_Summary
extends CRM_Report_Form
{
37 protected $_summary = NULL;
38 protected $_interval = NULL;
39 protected $_charts = array(
41 'barChart' => 'Bar Chart',
42 'pieChart' => 'Pie Chart',
44 protected $_add2groupSupported = FALSE;
46 protected $_customGroupExtends = array('Membership');
47 protected $_customGroupGroupBy = FALSE;
48 public $_drilldownReport = array('member/detail' => 'Link to Detail Report');
54 public function __construct() {
56 // UI for selecting columns to appear in the report list
57 // Array containing the columns, group_bys and filters build and provided to Form
59 // Check if CiviCampaign is a) enabled and b) has active campaigns
60 $config = CRM_Core_Config
::singleton();
61 $campaignEnabled = in_array("CiviCampaign", $config->enableComponents
);
62 if ($campaignEnabled) {
63 $getCampaigns = CRM_Campaign_BAO_Campaign
::getPermissionedCampaigns(NULL, NULL, TRUE, FALSE, TRUE);
64 $this->activeCampaigns
= $getCampaigns['campaigns'];
65 asort($this->activeCampaigns
);
68 $this->_columns
= array(
69 'civicrm_membership' => array(
70 'dao' => 'CRM_Member_DAO_MembershipType',
71 'grouping' => 'member-fields',
73 'membership_type_id' => array(
74 'title' => 'Membership Type',
80 'title' => ts('Member Since'),
81 'type' => CRM_Utils_Type
::T_DATE
,
82 'operatorType' => CRM_Report_Form
::OP_DATE
,
84 'membership_start_date' => array(
85 'name' => 'start_date',
86 'title' => ts('Membership Start Date'),
87 'type' => CRM_Utils_Type
::T_DATE
,
88 'operatorType' => CRM_Report_Form
::OP_DATE
,
90 'membership_end_date' => array(
92 'title' => ts('Membership End Date'),
93 'type' => CRM_Utils_Type
::T_DATE
,
94 'operatorType' => CRM_Report_Form
::OP_DATE
,
96 'owner_membership_id' => array(
97 'title' => ts('Membership Owner ID'),
98 'type' => CRM_Utils_Type
::T_INT
,
99 'operatorType' => CRM_Report_Form
::OP_INT
,
101 'membership_type_id' => array(
102 'title' => ts('Membership Type'),
103 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
104 'options' => CRM_Member_PseudoConstant
::membershipType(),
106 'status_id' => array(
107 'title' => ts('Membership Status'),
108 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
109 'options' => CRM_Member_PseudoConstant
::membershipStatus(NULL, NULL, 'label'),
112 'group_bys' => array(
113 'join_date' => array(
114 'title' => ts('Member Since'),
120 'membership_type_id' => array(
121 'title' => 'Membership Type',
127 'civicrm_contact' => array(
128 'dao' => 'CRM_Contact_DAO_Contact',
130 'contact_id' => array(
131 'no_display' => TRUE,
133 'contact_type' => array(
134 'title' => ts('Contact Type'),
136 'contact_sub_type' => array(
137 'title' => ts('Contact Subtype'),
141 'civicrm_contribution' => array(
142 'dao' => 'CRM_Contribute_DAO_Contribution',
146 'no_display' => TRUE,
148 'total_amount' => array(
149 'title' => ts('Amount Statistics'),
151 'statistics' => array(
152 'sum' => ts('Total Payments Made'),
153 'count' => ts('Contribution Count'),
154 'avg' => ts('Average'),
160 'title' => 'Currency',
161 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
162 'options' => CRM_Core_OptionGroup
::values('currencies_enabled'),
164 'type' => CRM_Utils_Type
::T_STRING
,
166 'contribution_status_id' => array(
167 'title' => ts('Contribution Status'),
168 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
169 'options' => CRM_Contribute_PseudoConstant
::contributionStatus(),
172 'grouping' => 'member-fields',
175 $this->_tagFilter
= TRUE;
177 // If we have a campaign, build out the relevant elements
178 if ($campaignEnabled && !empty($this->activeCampaigns
)) {
179 $this->_columns
['civicrm_membership']['fields']['campaign_id'] = array(
180 'title' => 'Campaign',
181 'default' => 'false',
183 $this->_columns
['civicrm_membership']['filters']['campaign_id'] = array(
184 'title' => ts('Campaign'),
185 'operatorType' => CRM_Report_Form
::OP_MULTISELECT
,
186 'options' => $this->activeCampaigns
,
188 $this->_columns
['civicrm_membership']['group_bys']['campaign_id'] = array('title' => ts('Campaign'));
191 $this->_groupFilter
= TRUE;
192 $this->_currencyColumn
= 'civicrm_contribution_currency';
193 parent
::__construct();
196 public function select() {
199 $this->_columnHeaders
= array();
200 $select[] = " COUNT( DISTINCT {$this->_aliases['civicrm_membership']}.id ) as civicrm_membership_member_count";
201 $select['joinDate'] = " {$this->_aliases['civicrm_membership']}.join_date as civicrm_membership_member_join_date";
202 $this->_columnHeaders
["civicrm_membership_member_join_date"] = array(
203 'title' => ts('Member Since'),
204 'type' => CRM_Utils_Type
::T_DATE
,
206 foreach ($this->_columns
as $tableName => $table) {
207 if (array_key_exists('group_bys', $table)) {
208 foreach ($table['group_bys'] as $fieldName => $field) {
209 if (!empty($this->_params
['group_bys'][$fieldName])) {
211 switch (CRM_Utils_Array
::value($fieldName, $this->_params
['group_bys_freq'])) {
213 $select[] = "DATE_SUB({$field['dbAlias']}, INTERVAL WEEKDAY({$field['dbAlias']}) DAY) AS {$tableName}_{$fieldName}_start";
215 $select[] = "YEARWEEK({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
216 $select[] = "WEEKOFYEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
217 $field['title'] = 'Week';
221 $select[] = "MAKEDATE(YEAR({$field['dbAlias']}), 1) AS {$tableName}_{$fieldName}_start";
222 $select[] = "YEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
223 $select[] = "YEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
224 $field['title'] = 'Year';
228 $select[] = "DATE_SUB({$field['dbAlias']}, INTERVAL (DAYOFMONTH({$field['dbAlias']})-1) DAY) as {$tableName}_{$fieldName}_start";
229 $select[] = "MONTH({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
230 $select[] = "MONTHNAME({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
231 $field['title'] = 'Month';
235 $select[] = "STR_TO_DATE(CONCAT( 3 * QUARTER( {$field['dbAlias']} ) -2 , '/', '1', '/', YEAR( {$field['dbAlias']} ) ), '%m/%d/%Y') AS {$tableName}_{$fieldName}_start";
236 $select[] = "QUARTER({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
237 $select[] = "QUARTER({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
238 $field['title'] = 'Quarter';
241 if (!empty($this->_params
['group_bys_freq'][$fieldName])) {
242 $this->_interval
= $field['title'];
243 $this->_columnHeaders
["{$tableName}_{$fieldName}_start"]['title'] = $field['title'] . ' Beginning';
244 $this->_columnHeaders
["{$tableName}_{$fieldName}_start"]['type'] = $field['type'];
245 $this->_columnHeaders
["{$tableName}_{$fieldName}_start"]['group_by'] = $this->_params
['group_bys_freq'][$fieldName];
247 // just to make sure these values are transfered to rows.
248 // since we need that for calculation purpose,
249 // e.g making subtotals look nicer or graphs
250 $this->_columnHeaders
["{$tableName}_{$fieldName}_interval"] = array('no_display' => TRUE);
251 $this->_columnHeaders
["{$tableName}_{$fieldName}_subtotal"] = array('no_display' => TRUE);
259 if (array_key_exists('fields', $table)) {
260 foreach ($table['fields'] as $fieldName => $field) {
261 if (!empty($field['required']) ||
262 !empty($this->_params
['fields'][$fieldName])
265 // only include statistics columns if set
266 if (!empty($field['statistics'])) {
267 $this->_statFields
[] = 'civicrm_membership_member_count';
268 foreach ($field['statistics'] as $stat => $label) {
269 switch (strtolower($stat)) {
271 $select[] = "IFNULL(SUM({$field['dbAlias']}), 0) as {$tableName}_{$fieldName}_{$stat}";
272 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
273 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
274 $this->_statFields
[] = "{$tableName}_{$fieldName}_{$stat}";
278 $select[] = "COUNT({$field['dbAlias']}) as {$tableName}_{$fieldName}_{$stat}";
279 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['type'] = CRM_Utils_Type
::T_INT
;
280 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
281 $this->_statFields
[] = "{$tableName}_{$fieldName}_{$stat}";
285 $select[] = "IFNULL(ROUND(AVG({$field['dbAlias']}),2), 0) as {$tableName}_{$fieldName}_{$stat}";
286 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
287 $this->_columnHeaders
["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
288 $this->_statFields
[] = "{$tableName}_{$fieldName}_{$stat}";
293 elseif ($fieldName == 'membership_type_id') {
294 if (empty($this->_params
['group_bys']['membership_type_id']) &&
295 !empty($this->_params
['group_bys']['join_date'])
297 $select[] = "GROUP_CONCAT(DISTINCT {$field['dbAlias']} ORDER BY {$field['dbAlias']} ) as {$tableName}_{$fieldName}";
300 $select[] = "{$field['dbAlias']} as {$tableName}_{$fieldName}";
302 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['title'] = $field['title'];
303 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['operatorType'] = CRM_Utils_Array
::value('operatorType', $field);
306 $select[] = "{$field['dbAlias']} as {$tableName}_{$fieldName}";
307 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['title'] = $field['title'];
308 $this->_columnHeaders
["{$tableName}_{$fieldName}"]['operatorType'] = CRM_Utils_Array
::value('operatorType', $field);
313 $this->_columnHeaders
["civicrm_membership_member_count"] = array(
314 'title' => ts('Member Count'),
315 'type' => CRM_Utils_Type
::T_INT
,
318 //If grouping is availabled then remove join date from field
320 unset($select['joinDate']);
321 unset($this->_columnHeaders
["civicrm_membership_member_join_date"]);
323 $this->_select
= "SELECT " . implode(', ', $select) . " ";
326 public function from() {
328 FROM civicrm_membership {$this->_aliases['civicrm_membership']}
330 LEFT JOIN civicrm_contact {$this->_aliases['civicrm_contact']} ON ( {$this->_aliases['civicrm_membership']}.contact_id = {$this->_aliases['civicrm_contact']}.id )
332 LEFT JOIN civicrm_membership_status
333 ON ({$this->_aliases['civicrm_membership']}.status_id = civicrm_membership_status.id )
334 LEFT JOIN civicrm_membership_payment payment
335 ON ( {$this->_aliases['civicrm_membership']}.id = payment.membership_id )
336 LEFT JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
337 ON payment.contribution_id = {$this->_aliases['civicrm_contribution']}.id";
340 public function where() {
341 $this->_whereClauses
[] = "{$this->_aliases['civicrm_membership']}.is_test = 0 AND
342 {$this->_aliases['civicrm_contact']}.is_deleted = 0";
346 public function groupBy() {
347 $this->_groupBy
= "";
348 if (is_array($this->_params
['group_bys']) &&
349 !empty($this->_params
['group_bys'])
351 foreach ($this->_columns
as $tableName => $table) {
352 if (array_key_exists('group_bys', $table)) {
353 foreach ($table['group_bys'] as $fieldName => $field) {
354 if (!empty($this->_params
['group_bys'][$fieldName])) {
355 if (!empty($field['chart'])) {
356 $this->assign('chartSupported', TRUE);
358 if (!empty($table['group_bys'][$fieldName]['frequency']) &&
359 !empty($this->_params
['group_bys_freq'][$fieldName])
362 $append = "YEAR({$field['dbAlias']}),";
363 if (in_array(strtolower($this->_params
['group_bys_freq'][$fieldName]),
368 $this->_groupBy
[] = "$append {$this->_params['group_bys_freq'][$fieldName]}({$field['dbAlias']})";
372 $this->_groupBy
[] = $field['dbAlias'];
379 $this->_rollup
= ' WITH ROLLUP';
380 $this->_groupBy
= 'GROUP BY ' . implode(', ', $this->_groupBy
) .
381 " {$this->_rollup} ";
384 $this->_groupBy
= "GROUP BY {$this->_aliases['civicrm_membership']}.join_date";
393 public function statistics(&$rows) {
394 $statistics = parent
::statistics($rows);
396 SELECT COUNT({$this->_aliases['civicrm_contribution']}.total_amount ) as count,
397 IFNULL(SUM({$this->_aliases['civicrm_contribution']}.total_amount ), 0) as amount,
398 IFNULL(ROUND(AVG({$this->_aliases['civicrm_contribution']}.total_amount), 2),0) as avg,
399 COUNT( DISTINCT {$this->_aliases['civicrm_membership']}.id ) as memberCount,
400 {$this->_aliases['civicrm_contribution']}.currency as currency
403 $sql = "{$select} {$this->_from} {$this->_where}
404 GROUP BY {$this->_aliases['civicrm_contribution']}.currency
407 $dao = CRM_Core_DAO
::executeQuery($sql);
409 $totalAmount = $average = array();
410 $count = $memberCount = 0;
411 while ($dao->fetch()) {
412 $totalAmount[] = CRM_Utils_Money
::format($dao->amount
, $dao->currency
) . "(" . $dao->count
. ")";
413 $average[] = CRM_Utils_Money
::format($dao->avg
, $dao->currency
);
414 $count +
= $dao->count
;
415 $memberCount +
= $dao->memberCount
;
417 $statistics['counts']['amount'] = array(
418 'title' => ts('Total Amount'),
419 'value' => implode(', ', $totalAmount),
420 'type' => CRM_Utils_Type
::T_STRING
,
422 $statistics['counts']['count'] = array(
423 'title' => ts('Total Contributions'),
426 $statistics['counts']['memberCount'] = array(
427 'title' => ts('Total Members'),
428 'value' => $memberCount,
430 $statistics['counts']['avg'] = array(
431 'title' => ts('Average'),
432 'value' => implode(', ', $average),
433 'type' => CRM_Utils_Type
::T_STRING
,
436 if (!(int) $statistics['counts']['amount']['value']) {
437 //if total amount is zero then hide Chart Options
438 $this->assign('chartSupported', FALSE);
444 public function postProcess() {
445 parent
::postProcess();
451 public function buildChart(&$rows) {
452 $graphRows = array();
454 $membershipTypeValues = CRM_Member_PseudoConstant
::membershipType();
455 $isMembershipType = CRM_Utils_Array
::value('membership_type_id', $this->_params
['group_bys']);
456 $isJoiningDate = CRM_Utils_Array
::value('join_date', $this->_params
['group_bys']);
457 if (!empty($this->_params
['charts'])) {
458 foreach ($rows as $key => $row) {
459 if (!($row['civicrm_membership_join_date_subtotal'] &&
460 $row['civicrm_membership_membership_type_id']
465 if ($isMembershipType) {
466 $join_date = CRM_Utils_Array
::value('civicrm_membership_join_date_start', $row);
467 $displayInterval = CRM_Utils_Array
::value('civicrm_membership_join_date_interval', $row);
469 list($year, $month) = explode('-', $join_date);
471 if (!empty($row['civicrm_membership_join_date_subtotal'])) {
473 switch ($this->_interval
) {
475 $displayRange = $displayInterval . ' ' . $year;
479 $displayRange = 'Quarter ' . $displayInterval . ' of ' . $year;
483 $displayRange = 'Week ' . $displayInterval . ' of ' . $year;
487 $displayRange = $year;
490 $membershipType = $displayRange . "-" .
491 $membershipTypeValues[$row['civicrm_membership_membership_type_id']];
495 $membershipType = $membershipTypeValues[$row['civicrm_membership_membership_type_id']];
498 $interval[$membershipType] = $membershipType;
499 $display[$membershipType] = $row['civicrm_contribution_total_amount_sum'];
502 $graphRows['receive_date'][] = CRM_Utils_Array
::value('civicrm_membership_join_date_start', $row);
503 $graphRows[$this->_interval
][] = CRM_Utils_Array
::value('civicrm_membership_join_date_interval', $row);
504 $graphRows['value'][] = $row['civicrm_contribution_total_amount_sum'];
510 if ($isMembershipType) {
511 $graphRows['value'] = $display;
513 'legend' => 'Membership Summary',
514 'xname' => 'Member Since / Member Type',
517 CRM_Utils_OpenFlashChart
::reportChart($graphRows, $this->_params
['charts'], $interval, $chartInfo);
520 CRM_Utils_OpenFlashChart
::chart($graphRows, $this->_params
['charts'], $this->_interval
);
523 $this->assign('chartType', $this->_params
['charts']);
527 * Alter display of rows.
529 * Iterate through the rows retrieved via SQL and make changes for display purposes,
530 * such as rendering contacts as links.
533 * Rows generated by SQL, with an array for each row.
535 public function alterDisplay(&$rows) {
537 foreach ($rows as $rowNum => $row) {
538 // make count columns point to detail report
539 if (!empty($this->_params
['group_bys']['join_date']) &&
540 !empty($row['civicrm_membership_join_date_start']) &&
541 $row['civicrm_membership_join_date_start'] &&
542 $row['civicrm_membership_join_date_subtotal']
545 $dateStart = CRM_Utils_Date
::customFormat($row['civicrm_membership_join_date_start'], '%Y%m%d');
546 $endDate = new DateTime($dateStart);
549 list($dateEnd['Y'], $dateEnd['M'], $dateEnd['d']) = explode(':', $endDate->format('Y:m:d'));
551 switch (strtolower($this->_params
['group_bys_freq']['join_date'])) {
553 $dateEnd = date("Ymd", mktime(0, 0, 0, $dateEnd['M'] +
1,
554 $dateEnd['d'] - 1, $dateEnd['Y']
559 $dateEnd = date("Ymd", mktime(0, 0, 0, $dateEnd['M'],
560 $dateEnd['d'] - 1, $dateEnd['Y'] +
1
565 $dateEnd = date("Ymd", mktime(0, 0, 0, $dateEnd['M'],
566 $dateEnd['d'] +
6, $dateEnd['Y']
571 $dateEnd = date("Ymd", mktime(0, 0, 0, $dateEnd['M'] +
3,
572 $dateEnd['d'] - 1, $dateEnd['Y']
577 if (!empty($this->_params
['group_bys']['membership_type_id']) &&
578 $typeID = $row['civicrm_membership_membership_type_id']
580 $typeUrl = "&tid_op=in&tid_value={$typeID}";
583 if (!empty($this->_params
['status_id_value'])) {
584 $statusUrl = "&sid_op=in&sid_value=" .
585 implode(",", $this->_params
['status_id_value']);
587 $url = CRM_Report_Utils_Report
::getNextUrl('member/detail',
588 "reset=1&force=1&join_date_from={$dateStart}&join_date_to={$dateEnd}{$typeUrl}{$statusUrl}",
589 $this->_absoluteUrl
, $this->_id
, $this->_drilldownReport
591 $row['civicrm_membership_join_date_start'] = CRM_Utils_Date
::format($row['civicrm_membership_join_date_start']);
592 $rows[$rowNum]['civicrm_membership_join_date_start_link'] = $url;
593 $rows[$rowNum]['civicrm_membership_join_date_start_hover'] = ts("Lists Summary of Memberships for this date unit.");
598 // handle Membership Types
599 if (array_key_exists('civicrm_membership_membership_type_id', $row)) {
600 if ($value = $row['civicrm_membership_membership_type_id']) {
601 $value = explode(',', $value);
602 foreach ($value as $key => $id) {
603 $value[$key] = CRM_Member_PseudoConstant
::membershipType($id, FALSE);
605 $rows[$rowNum]['civicrm_membership_membership_type_id'] = implode(' , ', $value);
610 // make subtotals look nicer
611 if (array_key_exists('civicrm_membership_join_date_subtotal', $row) &&
612 !$row['civicrm_membership_join_date_subtotal']
614 $this->fixSubTotalDisplay($rows[$rowNum], $this->_statFields
);
617 elseif (array_key_exists('civicrm_membership_join_date_subtotal', $row) &&
618 $row['civicrm_membership_join_date_subtotal'] &&
619 !$row['civicrm_membership_membership_type_id']
621 $this->fixSubTotalDisplay($rows[$rowNum], $this->_statFields
, FALSE);
622 $rows[$rowNum]['civicrm_membership_membership_type_id'] = '<b>SubTotal</b>';
626 // If using campaigns, convert campaign_id to campaign title
627 if (array_key_exists('civicrm_membership_campaign_id', $row)) {
628 if ($value = $row['civicrm_membership_campaign_id']) {
629 $rows[$rowNum]['civicrm_membership_campaign_id'] = $this->activeCampaigns
[$value];
634 // skip looking further in rows, if first row itself doesn't
635 // have the column we need