Button style fix
[civicrm-core.git] / CRM / Report / Form.php
CommitLineData
6a488035 1<?php
6a488035 2/*
36241b02 3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
db4cd986 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
db4cd986 7 +--------------------------------------------------------------------+
6a488035
TO
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 |
36241b02 25 +--------------------------------------------------------------------+
e70a7fc0 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * $Id$
33 *
34 */
35class CRM_Report_Form extends CRM_Core_Form {
7da04cde 36 const ROW_COUNT_LIMIT = 50;
6a488035
TO
37
38 /**
39 * Operator types - used for displaying filter elements
40 */
7da04cde 41 const
9d72cede 42 OP_INT = 1,
6a488035 43 OP_STRING = 2,
9d72cede 44 OP_DATE = 4,
6a488035 45 OP_DATETIME = 5,
9d72cede 46 OP_FLOAT = 8,
6a488035
TO
47 OP_SELECT = 64,
48 OP_MULTISELECT = 65,
49 OP_MULTISELECT_SEPARATOR = 66,
2107cde9
CW
50 OP_MONTH = 128,
51 OP_ENTITYREF = 256;
6a488035
TO
52
53 /**
54 * The id of the report instance
55 *
56 * @var integer
57 */
58 protected $_id;
59
60 /**
61 * The id of the report template
62 *
63 * @var integer;
64 */
65 protected $_templateID;
66
67 /**
68 * The report title
69 *
70 * @var string
71 */
72 protected $_title;
73 protected $_noFields = FALSE;
74
75 /**
76 * The set of all columns in the report. An associative array
537c70b8 77 * with column name as the key and attributes as the value
6a488035
TO
78 *
79 * @var array
80 */
81 protected $_columns = array();
82
83 /**
84 * The set of filters in the report
85 *
86 * @var array
87 */
88 protected $_filters = array();
89
90 /**
91 * The set of optional columns in the report
92 *
93 * @var array
94 */
95 protected $_options = array();
96
97 protected $_defaults = array();
98
688d37c6 99 /**
6a488035
TO
100 * By default most reports hide contact id.
101 * Setting this to true makes it available
102 */
103 protected $_exposeContactID = TRUE;
104
105 /**
106 * Set of statistic fields
107 *
108 * @var array
109 */
110 protected $_statFields = array();
111
112 /**
113 * Set of statistics data
114 *
115 * @var array
116 */
117 protected $_statistics = array();
118
119 /**
120 * List of fields not to be repeated during display
121 *
122 * @var array
123 */
124 protected $_noRepeats = array();
125
126 /**
127 * List of fields not to be displayed
128 *
129 * @var array
130 */
131 protected $_noDisplay = array();
132
133 /**
134 * Object type that a custom group extends
135 *
136 * @var null
137 */
138 protected $_customGroupExtends = NULL;
aa1aa08e 139 protected $_customGroupExtendsJoin = array();
6a488035
TO
140 protected $_customGroupFilters = TRUE;
141 protected $_customGroupGroupBy = FALSE;
9d72cede 142 protected $_customGroupJoin = 'LEFT JOIN';
6a488035
TO
143
144 /**
100fef9d 145 * Build tags filter
6a488035
TO
146 */
147 protected $_tagFilter = FALSE;
148
149 /**
ed795723
JM
150 * specify entity table for tags filter
151 *
152 */
153 protected $_tagFilterTable = 'civicrm_contact';
154
6a488035
TO
155 /**
156 * build groups filter
157 *
f587aa17 158 * @var bool
6a488035
TO
159 */
160 protected $_groupFilter = FALSE;
161
162 /**
163 * Navigation fields
164 *
165 * @var array
166 */
167 public $_navigation = array();
168
169 public $_drilldownReport = array();
170
171 /**
172 * An attribute for checkbox/radio form field layout
173 *
174 * @var array
175 */
176 protected $_fourColumnAttribute = array(
9d72cede
EM
177 '</td><td width="25%">',
178 '</td><td width="25%">',
179 '</td><td width="25%">',
180 '</tr><tr><td>',
6a488035
TO
181 );
182
183 protected $_force = 1;
184
185 protected $_params = NULL;
186 protected $_formValues = NULL;
187 protected $_instanceValues = NULL;
188
189 protected $_instanceForm = FALSE;
190 protected $_criteriaForm = FALSE;
191
192 protected $_instanceButtonName = NULL;
193 protected $_createNewButtonName = NULL;
194 protected $_printButtonName = NULL;
195 protected $_pdfButtonName = NULL;
196 protected $_csvButtonName = NULL;
197 protected $_groupButtonName = NULL;
198 protected $_chartButtonName = NULL;
199 protected $_csvSupported = TRUE;
200 protected $_add2groupSupported = TRUE;
201 protected $_groups = NULL;
9b0380d9 202 protected $_grandFlag = FALSE;
6a488035
TO
203 protected $_rowsFound = NULL;
204 protected $_selectAliases = array();
205 protected $_rollup = NULL;
6f900755 206
f587aa17
EM
207 /**
208 * @var array
209 */
210 protected $_aliases = array();
211
212 /**
213 * @var string
214 */
215 protected $_where;
216
217 /**
218 * @var string
219 */
220 protected $_from;
221
6f900755
E
222 /**
223 * SQL Limit clause
224 * @var string
225 */
6a488035 226 protected $_limit = NULL;
6f900755
E
227
228 /**
229 * This can be set to specify a limit to the number of rows
230 * Since it is currently envisaged as part of the api usage it is only being applied
231 * when $_output mode is not 'html' or 'group' so as not to have to interpret / mess with that part
232 * of the code (see limit() fn
233 * @var integer
234 */
235 protected $_limitValue = NULL;
236
237 /**
238 * This can be set to specify row offset
239 * See notes on _limitValue
240 * @var integer
241 */
242 protected $_offsetValue = NULL;
cdb44cba
EM
243 /**
244 * @var null
245 */
44d59d04 246 protected $_sections = NULL;
6a488035
TO
247 protected $_autoIncludeIndexedFieldsAsOrderBys = 0;
248 protected $_absoluteUrl = FALSE;
249
ae555e90
DS
250 /**
251 * Flag to indicate if result-set is to be stored in a class variable which could be retrieved using getResultSet() method.
252 *
253 * @var boolean
254 */
255 protected $_storeResultSet = FALSE;
256
257 /**
258 * When _storeResultSet Flag is set use this var to store result set in form of array
259 *
260 * @var boolean
261 */
262 protected $_resultSet = array();
263
6a488035
TO
264 /**
265 * To what frequency group-by a date column
266 *
267 * @var array
268 */
269 protected $_groupByDateFreq = array(
270 'MONTH' => 'Month',
271 'YEARWEEK' => 'Week',
272 'QUARTER' => 'Quarter',
273 'YEAR' => 'Year',
274 );
275
276 /**
277 * Variables to hold the acl inner join and where clause
278 */
279 protected $_aclFrom = NULL;
280 protected $_aclWhere = NULL;
281
282 /**
283 * Array of DAO tables having columns included in SELECT or ORDER BY clause
284 *
285 * @var array
286 */
287 protected $_selectedTables;
288
c58f66e0 289 /**
537c70b8 290 * Output mode e.g 'print', 'csv', 'pdf'
c58f66e0
E
291 * @var string
292 */
f63fae91 293 protected $_outputMode;
c58f66e0 294
6a488035
TO
295 public $_having = NULL;
296 public $_select = NULL;
1f220d30 297 public $_selectClauses = array();
6a488035
TO
298 public $_columnHeaders = array();
299 public $_orderBy = NULL;
f2947aea 300 public $_orderByFields = array();
9d72cede 301 public $_orderByArray = array();
6a488035 302 public $_groupBy = NULL;
adfe2750 303 public $_whereClauses = array();
304 public $_havingClauses = array();
1c4d8c3e 305
dbb4a0f9 306 /**
100fef9d 307 * DashBoardRowCount Dashboard row count
dbb4a0f9
PN
308 * @var Integer
309 */
310 public $_dashBoardRowCount;
311
c58f66e0
E
312 /**
313 * Is this being called without a form controller (ie. the report is being render outside the normal form
314 * - e.g the api is retrieving the rows
315 * @var boolean
316 */
317 public $noController = FALSE;
318
7a961f19 319 /**
320 * Variable to hold the currency alias
321 */
322 protected $_currencyColumn = NULL;
6a488035 323
2fe3c48d
EM
324 /**
325 * @var string
326 */
327 protected $_interval;
328
329 /**
330 * @var bool
331 */
332 protected $_sendmail;
333
334 /**
335 * @var int
336 */
337 protected $_chartId;
338
9907633b
EM
339 /**
340 * @var int
341 */
44d59d04 342 public $_section;
9907633b
EM
343
344 /**
345 * @var string Report description.
346 */
15ba35a3 347 public $_description;
9907633b
EM
348
349 /**
350 * @var bool Is an address field selected.
351 * This was intended to determine if the address table should be joined in
352 * The isTableSelected function is now preferred for this purpose
353 */
354 protected $_addressField;
355
356 /**
357 * @var bool Is an email field selected.
358 * This was intended to determine if the email table should be joined in
359 * The isTableSelected function is now preferred for this purpose
360 */
361 protected $_emailField;
362
363 /**
364 * @var bool Is a phone field selected.
365 * This was intended to determine if the phone table should be joined in
366 * The isTableSelected function is now preferred for this purpose
367 */
368 protected $_phoneField;
369
370 /**
371 * @var bool Create new report instance? (or update existing) on save.
372 */
373 protected $_createNew;
374
6a488035 375 /**
6a488035 376 */
00be9182 377 public function __construct() {
6a488035
TO
378 parent::__construct();
379
380 // build tag filter
381 if ($this->_tagFilter) {
382 $this->buildTagFilter();
383 }
384 if ($this->_exposeContactID) {
385 if (array_key_exists('civicrm_contact', $this->_columns)) {
386 $this->_columns['civicrm_contact']['fields']['exposed_id'] = array(
387 'name' => 'id',
388 'title' => 'Contact ID',
389 'no_repeat' => TRUE,
390 );
391 }
392 }
393
394 if ($this->_groupFilter) {
395 $this->buildGroupFilter();
396 }
397
398 // Get all custom groups
cd43c5e3 399 $allGroups = CRM_Core_PseudoConstant::get('CRM_Core_DAO_CustomField', 'custom_group_id');
6a488035 400
4f8ec8be
DS
401 // Get the custom groupIds for which the user has VIEW permission
402 // If the user has 'access all custom data' permission, we'll leave $permCustomGroupIds empty
403 // and addCustomDataToColumns() will allow access to all custom groups.
404 $permCustomGroupIds = array();
405 if (!CRM_Core_Permission::check('access all custom data')) {
406 $permCustomGroupIds = CRM_ACL_API::group(CRM_Core_Permission::VIEW, NULL, 'civicrm_custom_group', $allGroups, NULL);
407 // do not allow custom data for reports if user doesn't have
408 // permission to access custom data.
409 if (!empty($this->_customGroupExtends) && empty($permCustomGroupIds)) {
410 $this->_customGroupExtends = array();
411 }
6a488035
TO
412 }
413
414 // merge custom data columns to _columns list, if any
415 $this->addCustomDataToColumns(TRUE, $permCustomGroupIds);
416
417 // add / modify display columns, filters ..etc
418 CRM_Utils_Hook::alterReportVar('columns', $this->_columns, $this);
7a961f19 419
420 //assign currencyColumn variable to tpl
421 $this->assign('currencyColumn', $this->_currencyColumn);
6a488035
TO
422 }
423
00be9182 424 public function preProcessCommon() {
971d41b1
CW
425 $this->_force
426 = CRM_Utils_Request::retrieve(
427 'force',
428 'Boolean',
429 CRM_Core_DAO::$_nullObject
e70a7fc0 430 );
77b97be7 431
971d41b1
CW
432 $this->_dashBoardRowCount
433 = CRM_Utils_Request::retrieve(
434 'rowCount',
435 'Integer',
436 CRM_Core_DAO::$_nullObject
e70a7fc0 437 );
6a488035
TO
438
439 $this->_section = CRM_Utils_Request::retrieve('section', 'Integer', CRM_Core_DAO::$_nullObject);
440
441 $this->assign('section', $this->_section);
442 CRM_Core_Region::instance('page-header')->add(array(
443 'markup' => sprintf('<!-- Report class: [%s] -->', htmlentities(get_class($this))),
444 ));
9d72cede 445 if (!$this->noController) {
c58f66e0 446 $this->setID($this->get('instanceId'));
6a488035 447
6a488035 448 if (!$this->_id) {
c58f66e0
E
449 $this->setID(CRM_Report_Utils_Report::getInstanceID());
450 if (!$this->_id) {
9d72cede 451 $this->setID(CRM_Report_Utils_Report::getInstanceIDForPath());
c58f66e0 452 }
6a488035 453 }
6a488035 454
c58f66e0
E
455 // set qfkey so that pager picks it up and use it in the "Next > Last >>" links.
456 // FIXME: Note setting it in $_GET doesn't work, since pager generates link based on QUERY_STRING
457 $_SERVER['QUERY_STRING'] .= "&qfKey={$this->controller->_key}";
458 }
6a488035
TO
459
460 if ($this->_id) {
461 $this->assign('instanceId', $this->_id);
462 $params = array('id' => $this->_id);
463 $this->_instanceValues = array();
0b25329b 464 CRM_Core_DAO::commonRetrieve('CRM_Report_DAO_ReportInstance',
6a488035
TO
465 $params,
466 $this->_instanceValues
467 );
468 if (empty($this->_instanceValues)) {
469 CRM_Core_Error::fatal("Report could not be loaded.");
470 }
7cab1323 471 $this->_title = $this->_instanceValues['title'];
6a488035
TO
472 if (!empty($this->_instanceValues['permission']) &&
473 (!(CRM_Core_Permission::check($this->_instanceValues['permission']) ||
474 CRM_Core_Permission::check('administer Reports')
475 ))
476 ) {
477 CRM_Utils_System::permissionDenied();
478 CRM_Utils_System::civiExit();
479 }
480
481 $formValues = CRM_Utils_Array::value('form_values', $this->_instanceValues);
482 if ($formValues) {
483 $this->_formValues = unserialize($formValues);
484 }
485 else {
486 $this->_formValues = NULL;
487 }
488
489 // lets always do a force if reset is found in the url.
a7488080 490 if (!empty($_REQUEST['reset'])) {
6a488035
TO
491 $this->_force = 1;
492 }
493
494 // set the mode
495 $this->assign('mode', 'instance');
496 }
c58f66e0 497 elseif (!$this->noController) {
6a488035
TO
498 list($optionValueID, $optionValue) = CRM_Report_Utils_Report::getValueIDFromUrl();
499 $instanceCount = CRM_Report_Utils_Report::getInstanceCount($optionValue);
500 if (($instanceCount > 0) && $optionValueID) {
501 $this->assign('instanceUrl',
502 CRM_Utils_System::url('civicrm/report/list',
503 "reset=1&ovid=$optionValueID"
504 )
505 );
506 }
507 if ($optionValueID) {
508 $this->_description = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $optionValueID, 'description');
509 }
510
511 // set the mode
512 $this->assign('mode', 'template');
513 }
514
515 // lets display the Report Settings section
516 $this->_instanceForm = $this->_force || $this->_id || (!empty($_POST));
517
518 // Do not display Report Settings section if administer Reports permission is absent OR
519 // if report instance is reserved and administer reserved reports absent
520 if (!CRM_Core_Permission::check('administer Reports') ||
9d72cede
EM
521 ($this->_instanceValues['is_reserved'] &&
522 !CRM_Core_Permission::check('administer reserved reports'))
523 ) {
6a488035
TO
524 $this->_instanceForm = FALSE;
525 }
526
527 $this->assign('criteriaForm', FALSE);
528 // Display Report Criteria section if user has access Report Criteria OR administer Reports AND report instance is not reserved
9d72cede
EM
529 if (CRM_Core_Permission::check('administer Reports') ||
530 CRM_Core_Permission::check('access Report Criteria')
531 ) {
532 if (!$this->_instanceValues['is_reserved'] ||
533 CRM_Core_Permission::check('administer reserved reports')
534 ) {
6a488035
TO
535 $this->assign('criteriaForm', TRUE);
536 $this->_criteriaForm = TRUE;
537 }
538 }
539
540 $this->_instanceButtonName = $this->getButtonName('submit', 'save');
541 $this->_createNewButtonName = $this->getButtonName('submit', 'next');
542 $this->_printButtonName = $this->getButtonName('submit', 'print');
543 $this->_pdfButtonName = $this->getButtonName('submit', 'pdf');
544 $this->_csvButtonName = $this->getButtonName('submit', 'csv');
545 $this->_groupButtonName = $this->getButtonName('submit', 'group');
546 $this->_chartButtonName = $this->getButtonName('submit', 'chart');
547 }
548
00be9182 549 public function addBreadCrumb() {
971d41b1
CW
550 $breadCrumbs
551 = array(
8f1445ea
DL
552 array(
553 'title' => ts('Report Templates'),
554 'url' => CRM_Utils_System::url('civicrm/admin/report/template/list', 'reset=1'),
21dfd5f5 555 ),
8f1445ea 556 );
6a488035
TO
557
558 CRM_Utils_System::appendBreadCrumb($breadCrumbs);
559 }
560
00be9182 561 public function preProcess() {
8f1445ea 562 $this->preProcessCommon();
6a488035 563
6a488035 564 if (!$this->_id) {
29fc2b79 565 $this->addBreadCrumb();
6a488035
TO
566 }
567
568 foreach ($this->_columns as $tableName => $table) {
569 // set alias
570 if (!isset($table['alias'])) {
9d72cede
EM
571 $this->_columns[$tableName]['alias'] = substr($tableName, 8) .
572 '_civireport';
6a488035
TO
573 }
574 else {
575 $this->_columns[$tableName]['alias'] = $table['alias'] . '_civireport';
576 }
577
578 $this->_aliases[$tableName] = $this->_columns[$tableName]['alias'];
579
c75e90fa 580 $daoOrBaoName = NULL;
6a488035
TO
581 // higher preference to bao object
582 if (array_key_exists('bao', $table)) {
c75e90fa 583 $daoOrBaoName = $table['bao'];
9d72cede 584 $expFields = $daoOrBaoName::exportableFields();
6a488035 585 }
9d72cede 586 elseif (array_key_exists('dao', $table)) {
c75e90fa 587 $daoOrBaoName = $table['dao'];
9d72cede 588 $expFields = $daoOrBaoName::export();
6a488035 589 }
9d72cede 590 else {
8edb9849 591 $expFields = array();
592 }
6a488035
TO
593
594 $doNotCopy = array('required');
595
596 $fieldGroups = array('fields', 'filters', 'group_bys', 'order_bys');
597 foreach ($fieldGroups as $fieldGrp) {
a7488080 598 if (!empty($table[$fieldGrp]) && is_array($table[$fieldGrp])) {
6a488035
TO
599 foreach ($table[$fieldGrp] as $fieldName => $field) {
600 // $name is the field name used to reference the BAO/DAO export fields array
601 $name = isset($field['name']) ? $field['name'] : $fieldName;
602
603 // Sometimes the field name key in the BAO/DAO export fields array is
604 // different from the actual database field name.
605 // Unset $field['name'] so that actual database field name can be obtained
606 // from the BAO/DAO export fields array.
607 unset($field['name']);
608
609 if (array_key_exists($name, $expFields)) {
610 foreach ($doNotCopy as $dnc) {
611 // unset the values we don't want to be copied.
612 unset($expFields[$name][$dnc]);
613 }
614 if (empty($field)) {
615 $this->_columns[$tableName][$fieldGrp][$fieldName] = $expFields[$name];
616 }
617 else {
618 foreach ($expFields[$name] as $property => $val) {
619 if (!array_key_exists($property, $field)) {
620 $this->_columns[$tableName][$fieldGrp][$fieldName][$property] = $val;
621 }
622 }
623 }
624 }
625
626 // fill other vars
a7488080 627 if (!empty($field['no_repeat'])) {
6a488035
TO
628 $this->_noRepeats[] = "{$tableName}_{$fieldName}";
629 }
a7488080 630 if (!empty($field['no_display'])) {
6a488035
TO
631 $this->_noDisplay[] = "{$tableName}_{$fieldName}";
632 }
633
634 // set alias = table-name, unless already set
971d41b1
CW
635 $alias = isset($field['alias']) ? $field['alias'] : (
636 isset($this->_columns[$tableName]['alias']) ? $this->_columns[$tableName]['alias'] : $tableName
6a488035
TO
637 );
638 $this->_columns[$tableName][$fieldGrp][$fieldName]['alias'] = $alias;
639
640 // set name = fieldName, unless already set
641 if (!isset($this->_columns[$tableName][$fieldGrp][$fieldName]['name'])) {
642 $this->_columns[$tableName][$fieldGrp][$fieldName]['name'] = $name;
643 }
644
645 // set dbAlias = alias.name, unless already set
646 if (!isset($this->_columns[$tableName][$fieldGrp][$fieldName]['dbAlias'])) {
971d41b1
CW
647 $this->_columns[$tableName][$fieldGrp][$fieldName]['dbAlias']
648 = $alias . '.' .
9d72cede 649 $this->_columns[$tableName][$fieldGrp][$fieldName]['name'];
6a488035
TO
650 }
651
c75e90fa 652 // a few auto fills for filters
c93f6d83 653 if ($fieldGrp == 'filters') {
c75e90fa
DS
654 // fill operator types
655 if (!array_key_exists('operatorType', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
656 switch (CRM_Utils_Array::value('type', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
657 case CRM_Utils_Type::T_MONEY:
658 case CRM_Utils_Type::T_FLOAT:
659 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_FLOAT;
660 break;
ea100cb5 661
c75e90fa
DS
662 case CRM_Utils_Type::T_INT:
663 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_INT;
664 break;
ea100cb5 665
c75e90fa 666 case CRM_Utils_Type::T_DATE:
c93f6d83 667 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_DATE;
c75e90fa 668 break;
ea100cb5 669
c75e90fa
DS
670 case CRM_Utils_Type::T_BOOLEAN:
671 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_SELECT;
672 if (!array_key_exists('options', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
971d41b1
CW
673 $this->_columns[$tableName][$fieldGrp][$fieldName]['options']
674 = array(
9d72cede
EM
675 '' => ts('Any'),
676 '0' => ts('No'),
21dfd5f5 677 '1' => ts('Yes'),
9d72cede 678 );
c75e90fa
DS
679 }
680 break;
ea100cb5 681
c75e90fa 682 default:
c93f6d83 683 if ($daoOrBaoName &&
9d72cede
EM
684 array_key_exists('pseudoconstant', $this->_columns[$tableName][$fieldGrp][$fieldName])
685 ) {
c75e90fa
DS
686 // with multiple options operator-type is generally multi-select
687 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT;
688 if (!array_key_exists('options', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
689 // fill options
690 $this->_columns[$tableName][$fieldGrp][$fieldName]['options'] = CRM_Core_PseudoConstant::get($daoOrBaoName, $fieldName);
691 }
692 }
693 break;
694 }
6a488035
TO
695 }
696 }
697 }
698 }
699 }
700
701 // copy filters to a separate handy variable
702 if (array_key_exists('filters', $table)) {
703 $this->_filters[$tableName] = $this->_columns[$tableName]['filters'];
704 }
705
706 if (array_key_exists('group_bys', $table)) {
707 $groupBys[$tableName] = $this->_columns[$tableName]['group_bys'];
708 }
709
710 if (array_key_exists('fields', $table)) {
711 $reportFields[$tableName] = $this->_columns[$tableName]['fields'];
712 }
713 }
714
715 if ($this->_force) {
716 $this->setDefaultValues(FALSE);
717 }
718
8f1445ea
DL
719 CRM_Report_Utils_Get::processFilter($this->_filters, $this->_defaults);
720 CRM_Report_Utils_Get::processGroupBy($groupBys, $this->_defaults);
721 CRM_Report_Utils_Get::processFields($reportFields, $this->_defaults);
6a488035
TO
722 CRM_Report_Utils_Get::processChart($this->_defaults);
723
724 if ($this->_force) {
725 $this->_formValues = $this->_defaults;
726 $this->postProcess();
727 }
728 }
729
74cf4551
EM
730 /**
731 * @param bool $freeze
732 *
733 * @return array
734 */
00be9182 735 public function setDefaultValues($freeze = TRUE) {
6a488035
TO
736 $freezeGroup = array();
737
738 // FIXME: generalizing form field naming conventions would reduce
739 // lots of lines below.
740 foreach ($this->_columns as $tableName => $table) {
741 if (array_key_exists('fields', $table)) {
742 foreach ($table['fields'] as $fieldName => $field) {
a7488080 743 if (empty($field['no_display'])) {
6a488035
TO
744 if (isset($field['required'])) {
745 // set default
746 $this->_defaults['fields'][$fieldName] = 1;
747
748 if ($freeze) {
749 // find element object, so that we could use quickform's freeze method
750 // for required elements
8f1445ea 751 $obj = $this->getElementFromGroup("fields", $fieldName);
6a488035
TO
752 if ($obj) {
753 $freezeGroup[] = $obj;
754 }
755 }
756 }
757 elseif (isset($field['default'])) {
758 $this->_defaults['fields'][$fieldName] = $field['default'];
759 }
760 }
761 }
762 }
763
764 if (array_key_exists('group_bys', $table)) {
765 foreach ($table['group_bys'] as $fieldName => $field) {
766 if (isset($field['default'])) {
a7488080 767 if (!empty($field['frequency'])) {
6a488035
TO
768 $this->_defaults['group_bys_freq'][$fieldName] = 'MONTH';
769 }
770 $this->_defaults['group_bys'][$fieldName] = $field['default'];
771 }
772 }
773 }
774 if (array_key_exists('filters', $table)) {
775 foreach ($table['filters'] as $fieldName => $field) {
776 if (isset($field['default'])) {
9d72cede
EM
777 if (CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_DATE
778 ) {
779 if (is_array($field['default'])) {
b0e34e7c 780 $this->_defaults["{$fieldName}_from"] = CRM_Utils_Array::value('from', $field['default']);
781 $this->_defaults["{$fieldName}_to"] = CRM_Utils_Array::value('to', $field['default']);
782 $this->_defaults["{$fieldName}_relative"] = 0;
783 }
9d72cede 784 else {
b0e34e7c 785 $this->_defaults["{$fieldName}_relative"] = $field['default'];
786 }
6a488035
TO
787 }
788 else {
789 $this->_defaults["{$fieldName}_value"] = $field['default'];
790 }
791 }
792 //assign default value as "in" for multiselect
793 //operator, To freeze the select element
9d72cede
EM
794 if (CRM_Utils_Array::value('operatorType', $field) ==
795 CRM_Report_Form::OP_MULTISELECT
796 ) {
6a488035
TO
797 $this->_defaults["{$fieldName}_op"] = 'in';
798 }
9d72cede
EM
799 if (CRM_Utils_Array::value('operatorType', $field) ==
800 CRM_Report_Form::OP_ENTITYREF
801 ) {
2107cde9
CW
802 $this->_defaults["{$fieldName}_op"] = 'in';
803 }
9d72cede
EM
804 elseif (CRM_Utils_Array::value('operatorType', $field) ==
805 CRM_Report_Form::OP_MULTISELECT_SEPARATOR
806 ) {
6a488035
TO
807 $this->_defaults["{$fieldName}_op"] = 'mhas';
808 }
809 elseif ($op = CRM_Utils_Array::value('default_op', $field)) {
810 $this->_defaults["{$fieldName}_op"] = $op;
811 }
812 }
813 }
814
8f1445ea
DL
815 if (
816 array_key_exists('order_bys', $table) &&
6a488035
TO
817 is_array($table['order_bys'])
818 ) {
6a488035 819 if (!array_key_exists('order_bys', $this->_defaults)) {
6a488035
TO
820 $this->_defaults['order_bys'] = array();
821 }
822 foreach ($table['order_bys'] as $fieldName => $field) {
8cc574cf 823 if (!empty($field['default']) || !empty($field['default_order']) ||
9d72cede
EM
824 CRM_Utils_Array::value('default_is_section', $field) ||
825 !empty($field['default_weight'])
826 ) {
6a488035
TO
827 $order_by = array(
828 'column' => $fieldName,
829 'order' => CRM_Utils_Array::value('default_order', $field, 'ASC'),
830 'section' => CRM_Utils_Array::value('default_is_section', $field, 0),
831 );
832
a7488080 833 if (!empty($field['default_weight'])) {
6a488035
TO
834 $this->_defaults['order_bys'][(int) $field['default_weight']] = $order_by;
835 }
836 else {
837 array_unshift($this->_defaults['order_bys'], $order_by);
838 }
839 }
840 }
841 }
842
843 foreach ($this->_options as $fieldName => $field) {
844 if (isset($field['default'])) {
845 $this->_defaults['options'][$fieldName] = $field['default'];
846 }
847 }
848 }
849
850 if (!empty($this->_submitValues)) {
851 $this->preProcessOrderBy($this->_submitValues);
852 }
853 else {
854 $this->preProcessOrderBy($this->_defaults);
855 }
856
857 // lets finish freezing task here itself
858 if (!empty($freezeGroup)) {
859 foreach ($freezeGroup as $elem) {
860 $elem->freeze();
861 }
862 }
863
864 if ($this->_formValues) {
865 $this->_defaults = array_merge($this->_defaults, $this->_formValues);
866 }
867
868 if ($this->_instanceValues) {
869 $this->_defaults = array_merge($this->_defaults, $this->_instanceValues);
870 }
871
872 CRM_Report_Form_Instance::setDefaultValues($this, $this->_defaults);
873
874 return $this->_defaults;
875 }
876
74cf4551 877 /**
8a925f33 878 * @param string $group
100fef9d 879 * @param string $grpFieldName
74cf4551
EM
880 *
881 * @return bool
882 */
00be9182 883 public function getElementFromGroup($group, $grpFieldName) {
6a488035
TO
884 $eleObj = $this->getElement($group);
885 foreach ($eleObj->_elements as $index => $obj) {
886 if ($grpFieldName == $obj->_attributes['name']) {
887 return $obj;
888 }
889 }
890 return FALSE;
891 }
892
c58f66e0
E
893 /**
894 * Setter for $_params
9d72cede 895 *
c58f66e0
E
896 * @param array $params
897 */
00be9182 898 public function setParams($params) {
c58f66e0
E
899 $this->_params = $params;
900 }
901
902 /**
903 * Setter for $_id
2a6da8d7 904 *
537c70b8 905 * @param int $instanceID
c58f66e0 906 */
537c70b8
EM
907 public function setID($instanceID) {
908 $this->_id = $instanceID;
c58f66e0
E
909 }
910
911 /**
912 * Setter for $_force
9d72cede
EM
913 *
914 * @param $isForce
915 */
00be9182 916 public function setForce($isForce) {
c58f66e0
E
917 $this->_force = $isForce;
918 }
6f900755
E
919
920 /**
921 * Setter for $_limitValue
9d72cede 922 *
d3e86119 923 * @param int $_limitValue
6f900755 924 */
00be9182 925 public function setLimitValue($_limitValue) {
6f900755
E
926 $this->_limitValue = $_limitValue;
927 }
928
929 /**
930 * Setter for $_offsetValue
9d72cede 931 *
d3e86119 932 * @param int $_offsetValue
6f900755 933 */
00be9182 934 public function setOffsetValue($_offsetValue) {
6f900755
E
935 $this->_offsetValue = $_offsetValue;
936 }
937
c58f66e0
E
938 /**
939 * Getter for $_defaultValues
a6c01b45 940 * @return array
c58f66e0 941 */
00be9182 942 public function getDefaultValues() {
c58f66e0
E
943 return $this->_defaults;
944 }
945
00be9182 946 public function addColumns() {
6a488035
TO
947 $options = array();
948 $colGroups = NULL;
949 foreach ($this->_columns as $tableName => $table) {
950 if (array_key_exists('fields', $table)) {
951 foreach ($table['fields'] as $fieldName => $field) {
8bdc861c 952 $groupTitle = '';
a7488080 953 if (empty($field['no_display'])) {
9d72cede 954 foreach (array('table', 'field') as $var) {
8bdc861c
DS
955 if (!empty(${$var}['grouping'])) {
956 if (!is_array(${$var}['grouping'])) {
957 $tableName = ${$var}['grouping'];
9d72cede
EM
958 }
959 else {
b80138e0
DS
960 $tableName = array_keys(${$var}['grouping']);
961 $tableName = $tableName[0];
962 $groupTitle = array_values(${$var}['grouping']);
963 $groupTitle = $groupTitle[0];
8bdc861c
DS
964 }
965 }
6a488035 966 }
8bdc861c
DS
967
968 if (!$groupTitle && isset($table['group_title'])) {
969 $groupTitle = $table['group_title'];
6a488035 970 }
6a488035 971
8bdc861c 972 $colGroups[$tableName]['fields'][$fieldName] = CRM_Utils_Array::value('title', $field);
8cc574cf 973 if ($groupTitle && empty($colGroups[$tableName]['group_title'])) {
8bdc861c 974 $colGroups[$tableName]['group_title'] = $groupTitle;
6a488035
TO
975 }
976
977 $options[$fieldName] = CRM_Utils_Array::value('title', $field);
978 }
979 }
980 }
981 }
982
983 $this->addCheckBox("fields", ts('Select Columns'), $options, NULL,
984 NULL, NULL, NULL, $this->_fourColumnAttribute, TRUE
985 );
986 $this->assign('colGroups', $colGroups);
987 }
988
00be9182 989 public function addFilters() {
c927c151 990 $filters = array();
6a488035
TO
991 $count = 1;
992 foreach ($this->_filters as $table => $attributes) {
993 foreach ($attributes as $fieldName => $field) {
994 // get ready with option value pair
1b36206c 995 // @ todo being able to specific options for a field (e.g a date field) in the field spec as an array rather than an override
996 // would be useful
997 $operations = $this->getOperationPair(
8f1445ea 998 CRM_Utils_Array::value('operatorType', $field),
1b36206c 999 $fieldName);
6a488035
TO
1000
1001 $filters[$table][$fieldName] = $field;
1002
1003 switch (CRM_Utils_Array::value('operatorType', $field)) {
1004 case CRM_Report_Form::OP_MONTH:
9d72cede
EM
1005 if (!array_key_exists('options', $field) ||
1006 !is_array($field['options']) || empty($field['options'])
1007 ) {
6a488035
TO
1008 // If there's no option list for this filter, define one.
1009 $field['options'] = array(
1010 1 => ts('January'),
1011 2 => ts('February'),
1012 3 => ts('March'),
1013 4 => ts('April'),
1014 5 => ts('May'),
1015 6 => ts('June'),
1016 7 => ts('July'),
1017 8 => ts('August'),
1018 9 => ts('September'),
1019 10 => ts('October'),
1020 11 => ts('November'),
1021 12 => ts('December'),
1022 );
1023 // Add this option list to this column _columns. This is
1024 // required so that filter statistics show properly.
1025 $this->_columns[$table]['filters'][$fieldName]['options'] = $field['options'];
1026 }
160d32e1
E
1027 case CRM_Report_Form::OP_MULTISELECT:
1028 case CRM_Report_Form::OP_MULTISELECT_SEPARATOR:
6a488035 1029 // assume a multi-select field
9d72cede
EM
1030 if (!empty($field['options']) ||
1031 $fieldName == 'state_province_id' || $fieldName == 'county_id'
1032 ) {
6a488035
TO
1033 $element = $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations);
1034 if (count($operations) <= 1) {
1035 $element->freeze();
1036 }
9d72cede
EM
1037 if ($fieldName == 'state_province_id' ||
1038 $fieldName == 'county_id'
1039 ) {
1040 $this->addChainSelect($fieldName . '_value', array(
86bd39be
TO
1041 'multiple' => TRUE,
1042 'label' => NULL,
21dfd5f5 1043 'class' => 'huge',
86bd39be 1044 ));
c927c151
CW
1045 }
1046 else {
1047 $this->addElement('select', "{$fieldName}_value", NULL, $field['options'], array(
1048 'style' => 'min-width:250px',
2107cde9 1049 'class' => 'crm-select2 huge',
c927c151
CW
1050 'multiple' => TRUE,
1051 'placeholder' => ts('- select -'),
1052 ));
1053 }
6a488035
TO
1054 }
1055 break;
1056
160d32e1 1057 case CRM_Report_Form::OP_SELECT:
6a488035
TO
1058 // assume a select field
1059 $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations);
9d72cede 1060 if (!empty($field['options'])) {
5a9a44d9 1061 $this->addElement('select', "{$fieldName}_value", NULL, $field['options']);
9d72cede 1062 }
6a488035
TO
1063 break;
1064
2107cde9
CW
1065 case CRM_Report_Form::OP_ENTITYREF:
1066 $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations);
1067 $this->setEntityRefDefaults($field, $table);
1068 $this->addEntityRef("{$fieldName}_value", NULL, $field['attributes']);
1069 break;
1070
160d32e1 1071 case CRM_Report_Form::OP_DATE:
6a488035 1072 // build datetime fields
9d72cede 1073 CRM_Core_Form_Date::buildDateRange($this, $fieldName, $count, '_from', '_to', 'From:', FALSE, $operations);
6a488035
TO
1074 $count++;
1075 break;
1076
160d32e1 1077 case CRM_Report_Form::OP_DATETIME:
6a488035 1078 // build datetime fields
9d72cede 1079 CRM_Core_Form_Date::buildDateRange($this, $fieldName, $count, '_from', '_to', 'From:', FALSE, $operations, 'searchDate', TRUE);
6a488035
TO
1080 $count++;
1081 break;
1082
160d32e1
E
1083 case CRM_Report_Form::OP_INT:
1084 case CRM_Report_Form::OP_FLOAT:
6a488035
TO
1085 // and a min value input box
1086 $this->add('text', "{$fieldName}_min", ts('Min'));
1087 // and a max value input box
1088 $this->add('text', "{$fieldName}_max", ts('Max'));
1089 default:
1090 // default type is string
1091 $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations,
1092 array('onchange' => "return showHideMaxMinVal( '$fieldName', this.value );")
1093 );
1094 // we need text box for value input
2107cde9 1095 $this->add('text', "{$fieldName}_value", NULL, array('class' => 'huge'));
6a488035
TO
1096 break;
1097 }
1098 }
1099 }
1100 $this->assign('filters', $filters);
1101 }
1102
00be9182 1103 public function addOptions() {
6a488035
TO
1104 if (!empty($this->_options)) {
1105 // FIXME: For now lets build all elements as checkboxes.
1106 // Once we clear with the format we can build elements based on type
1107
1108 $options = array();
1109 foreach ($this->_options as $fieldName => $field) {
1110 if ($field['type'] == 'select') {
1111 $this->addElement('select', "{$fieldName}", $field['title'], $field['options']);
1112 }
4c9b6178 1113 elseif ($field['type'] == 'checkbox') {
6a488035 1114 $options[$field['title']] = $fieldName;
52634dad
DS
1115 $this->addCheckBox($fieldName, NULL,
1116 $options, NULL,
1117 NULL, NULL, NULL, $this->_fourColumnAttribute
1118 );
6a488035
TO
1119 }
1120 }
6a488035 1121 }
52634dad 1122 $this->assign('otherOptions', $this->_options);
6a488035
TO
1123 }
1124
00be9182 1125 public function addChartOptions() {
6a488035
TO
1126 if (!empty($this->_charts)) {
1127 $this->addElement('select', "charts", ts('Chart'), $this->_charts, array('onchange' => 'disablePrintPDFButtons(this.value);'));
1128 $this->assign('charts', $this->_charts);
1129 $this->addElement('submit', $this->_chartButtonName, ts('View'));
1130 }
1131 }
1132
00be9182 1133 public function addGroupBys() {
6a488035
TO
1134 $options = $freqElements = array();
1135
1136 foreach ($this->_columns as $tableName => $table) {
1137 if (array_key_exists('group_bys', $table)) {
1138 foreach ($table['group_bys'] as $fieldName => $field) {
1139 if (!empty($field)) {
1140 $options[$field['title']] = $fieldName;
a7488080 1141 if (!empty($field['frequency'])) {
6a488035
TO
1142 $freqElements[$field['title']] = $fieldName;
1143 }
1144 }
1145 }
1146 }
1147 }
1148 $this->addCheckBox("group_bys", ts('Group by columns'), $options, NULL,
1149 NULL, NULL, NULL, $this->_fourColumnAttribute
1150 );
1151 $this->assign('groupByElements', $options);
1152
1153 foreach ($freqElements as $name) {
1154 $this->addElement('select', "group_bys_freq[$name]",
1155 ts('Frequency'), $this->_groupByDateFreq
1156 );
1157 }
1158 }
1159
00be9182 1160 public function addOrderBys() {
6a488035
TO
1161 $options = array();
1162 foreach ($this->_columns as $tableName => $table) {
1163
1164 // Report developer may define any column to order by; include these as order-by options
1165 if (array_key_exists('order_bys', $table)) {
1166 foreach ($table['order_bys'] as $fieldName => $field) {
1167 if (!empty($field)) {
1168 $options[$fieldName] = $field['title'];
1169 }
1170 }
1171 }
1172
971d41b1
CW
1173 // Add searchable custom fields as order-by options, if so requested
1174 // (These are already indexed, so allowing to order on them is cheap.)
6a488035 1175
9d72cede
EM
1176 if ($this->_autoIncludeIndexedFieldsAsOrderBys &&
1177 array_key_exists('extends', $table) && !empty($table['extends'])
1178 ) {
6a488035 1179 foreach ($table['fields'] as $fieldName => $field) {
a7488080 1180 if (empty($field['no_display'])) {
6a488035
TO
1181 $options[$fieldName] = $field['title'];
1182 }
1183 }
1184 }
1185 }
1186
1187 asort($options);
1188
1189 $this->assign('orderByOptions', $options);
1190
1191 if (!empty($options)) {
1192 $options = array(
971d41b1
CW
1193 '-' => ' - none - ',
1194 ) + $options;
6a488035
TO
1195 for ($i = 1; $i <= 5; $i++) {
1196 $this->addElement('select', "order_bys[{$i}][column]", ts('Order by Column'), $options);
9d72cede 1197 $this->addElement('select', "order_bys[{$i}][order]", ts('Order by Order'), array(
86bd39be 1198 'ASC' => 'Ascending',
21dfd5f5 1199 'DESC' => 'Descending',
86bd39be 1200 ));
6a488035 1201 $this->addElement('checkbox', "order_bys[{$i}][section]", ts('Order by Section'), FALSE, array('id' => "order_by_section_$i"));
5895cba0 1202 $this->addElement('checkbox', "order_bys[{$i}][pageBreak]", ts('Page Break'), FALSE, array('id' => "order_by_pagebreak_$i"));
6a488035
TO
1203 }
1204 }
1205 }
1206
00be9182 1207 public function buildInstanceAndButtons() {
6a488035
TO
1208 CRM_Report_Form_Instance::buildForm($this);
1209
1210 $label = $this->_id ? ts('Update Report') : ts('Create Report');
1211
1212 $this->addElement('submit', $this->_instanceButtonName, $label);
1213 $this->addElement('submit', $this->_printButtonName, ts('Print Report'));
1214 $this->addElement('submit', $this->_pdfButtonName, ts('PDF'));
1215
1216 if ($this->_id) {
9d72cede
EM
1217 $this->addElement('submit', $this->_createNewButtonName,
1218 ts('Save a Copy') . '...');
6a488035 1219 }
c9cf6554 1220 $this->assign('instanceForm', $this->_instanceForm);
6a488035
TO
1221
1222 $label = $this->_id ? ts('Print Report') : ts('Print Preview');
1223 $this->addElement('submit', $this->_printButtonName, $label);
1224
1225 $label = $this->_id ? ts('PDF') : ts('Preview PDF');
1226 $this->addElement('submit', $this->_pdfButtonName, $label);
1227
1228 $label = $this->_id ? ts('Export to CSV') : ts('Preview CSV');
1229
1230 if ($this->_csvSupported) {
1231 $this->addElement('submit', $this->_csvButtonName, $label);
1232 }
1233
9d72cede
EM
1234 if (CRM_Core_Permission::check('administer Reports') &&
1235 $this->_add2groupSupported
1236 ) {
6a488035 1237 $this->addElement('select', 'groups', ts('Group'),
9d72cede
EM
1238 array('' => ts('Add Contacts to Group')) +
1239 CRM_Core_PseudoConstant::nestedGroup(),
24431f7b 1240 array('class' => 'crm-select2 crm-action-menu action-icon-plus huge')
6a488035
TO
1241 );
1242 $this->assign('group', TRUE);
1243 }
1244
24431f7b 1245 $this->addElement('submit', $this->_groupButtonName, '', array('style' => 'display: none;'));
6a488035
TO
1246
1247 $this->addChartOptions();
1248 $this->addButtons(array(
1249 array(
1250 'type' => 'submit',
1251 'name' => ts('Preview Report'),
1252 'isDefault' => TRUE,
1253 ),
1254 )
1255 );
1256 }
1257
00be9182 1258 public function buildQuickForm() {
6a488035
TO
1259 $this->addColumns();
1260
1261 $this->addFilters();
1262
1263 $this->addOptions();
1264
1265 $this->addGroupBys();
1266
1267 $this->addOrderBys();
1268
1269 $this->buildInstanceAndButtons();
1270
1271 //add form rule for report
1272 if (is_callable(array(
9d72cede 1273 $this,
21dfd5f5 1274 'formRule',
9d72cede 1275 ))) {
6a488035
TO
1276 $this->addFormRule(array(get_class($this), 'formRule'), $this);
1277 }
1278 }
1279
74cf4551 1280 /**
537c70b8 1281 * A form rule function to ensure that fields selected in group_by
4f1f1f2a
CW
1282 * (if any) should only be the ones present in display/select fields criteria;
1283 * note: works if and only if any custom field selected in group_by.
537c70b8 1284 * @param array $fields
74cf4551
EM
1285 * @param array $ignoreFields
1286 *
1287 * @return array
1288 */
00be9182 1289 public function customDataFormRule($fields, $ignoreFields = array()) {
6a488035 1290 $errors = array();
9d72cede
EM
1291 if (!empty($this->_customGroupExtends) && $this->_customGroupGroupBy &&
1292 !empty($fields['group_bys'])
1293 ) {
6a488035 1294 foreach ($this->_columns as $tableName => $table) {
9d72cede
EM
1295 if ((substr($tableName, 0, 13) == 'civicrm_value' ||
1296 substr($tableName, 0, 12) == 'custom_value') &&
1297 !empty($this->_columns[$tableName]['fields'])
1298 ) {
6a488035
TO
1299 foreach ($this->_columns[$tableName]['fields'] as $fieldName => $field) {
1300 if (array_key_exists($fieldName, $fields['group_bys']) &&
1301 !array_key_exists($fieldName, $fields['fields'])
1302 ) {
1303 $errors['fields'] = "Please make sure fields selected in 'Group by Columns' section are also selected in 'Display Columns' section.";
1304 }
1305 elseif (array_key_exists($fieldName, $fields['group_bys'])) {
1306 foreach ($fields['fields'] as $fld => $val) {
9d72cede
EM
1307 if (!array_key_exists($fld, $fields['group_bys']) &&
1308 !in_array($fld, $ignoreFields)
1309 ) {
6a488035
TO
1310 $errors['fields'] = "Please ensure that fields selected in 'Display Columns' are also selected in 'Group by Columns' section.";
1311 }
1312 }
1313 }
1314 }
1315 }
1316 }
1317 }
1318 return $errors;
1319 }
1320
74cf4551 1321 /**
4f1f1f2a
CW
1322 * Note: $fieldName param allows inheriting class to build operationPairs
1323 * specific to a field.
74cf4551 1324 * @param string $type
8a925f33 1325 * @param string $fieldName
74cf4551
EM
1326 *
1327 * @return array
1328 */
00be9182 1329 public function getOperationPair($type = "string", $fieldName = NULL) {
6a488035
TO
1330 // FIXME: At some point we should move these key-val pairs
1331 // to option_group and option_value table.
6a488035 1332 switch ($type) {
160d32e1
E
1333 case CRM_Report_Form::OP_INT:
1334 case CRM_Report_Form::OP_FLOAT:
bed98343 1335
6c552737 1336 $result = array(
bc3f7f04 1337 'lte' => ts('Is less than or equal to'),
6a488035
TO
1338 'gte' => ts('Is greater than or equal to'),
1339 'bw' => ts('Is between'),
1340 'eq' => ts('Is equal to'),
1341 'lt' => ts('Is less than'),
1342 'gt' => ts('Is greater than'),
1343 'neq' => ts('Is not equal to'),
1344 'nbw' => ts('Is not between'),
1345 'nll' => ts('Is empty (Null)'),
1346 'nnll' => ts('Is not empty (Null)'),
1347 );
6c552737 1348 return $result;
6a488035 1349
160d32e1 1350 case CRM_Report_Form::OP_SELECT:
6c552737 1351 $result = array(
bc3f7f04 1352 'eq' => ts('Is equal to'),
1353 );
6c552737 1354 return $result;
6a488035 1355
160d32e1
E
1356 case CRM_Report_Form::OP_MONTH:
1357 case CRM_Report_Form::OP_MULTISELECT:
2107cde9 1358 case CRM_Report_Form::OP_ENTITYREF:
bed98343 1359
6c552737 1360 $result = array(
bc3f7f04 1361 'in' => ts('Is one of'),
6a488035
TO
1362 'notin' => ts('Is not one of'),
1363 );
6c552737 1364 return $result;
6a488035 1365
160d32e1 1366 case CRM_Report_Form::OP_DATE:
bed98343 1367
6c552737 1368 $result = array(
bc3f7f04 1369 'nll' => ts('Is empty (Null)'),
6a488035
TO
1370 'nnll' => ts('Is not empty (Null)'),
1371 );
6c552737 1372 return $result;
6a488035 1373
160d32e1 1374 case CRM_Report_Form::OP_MULTISELECT_SEPARATOR:
6a488035
TO
1375 // use this operator for the values, concatenated with separator. For e.g if
1376 // multiple options for a column is stored as ^A{val1}^A{val2}^A
6c552737 1377 $result = array(
bc3f7f04 1378 'mhas' => ts('Is one of'),
67788963 1379 'mnot' => ts('Is not one of'),
bc3f7f04 1380 );
6c552737 1381 return $result;
6a488035
TO
1382
1383 default:
1384 // type is string
6c552737 1385 $result = array(
bc3f7f04 1386 'has' => ts('Contains'),
6a488035
TO
1387 'sw' => ts('Starts with'),
1388 'ew' => ts('Ends with'),
1389 'nhas' => ts('Does not contain'),
1390 'eq' => ts('Is equal to'),
1391 'neq' => ts('Is not equal to'),
1392 'nll' => ts('Is empty (Null)'),
1393 'nnll' => ts('Is not empty (Null)'),
1394 );
6c552737 1395 return $result;
6a488035
TO
1396 }
1397 }
1398
00be9182 1399 public function buildTagFilter() {
ed795723 1400 $contactTags = CRM_Core_BAO_Tag::getTags($this->_tagFilterTable);
6a488035
TO
1401 if (!empty($contactTags)) {
1402 $this->_columns['civicrm_tag'] = array(
1403 'dao' => 'CRM_Core_DAO_Tag',
9d72cede
EM
1404 'filters' => array(
1405 'tagid' => array(
6a488035
TO
1406 'name' => 'tag_id',
1407 'title' => ts('Tag'),
1408 'tag' => TRUE,
1409 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
1410 'options' => $contactTags,
1411 ),
1412 ),
1413 );
1414 }
1415 }
1416
688d37c6 1417 /**
537c70b8 1418 * Adds group filters to _columns (called from _Construct
6a488035 1419 */
00be9182 1420 public function buildGroupFilter() {
6a488035 1421 $this->_columns['civicrm_group']['filters'] = array(
9d72cede 1422 'gid' => array(
6a488035
TO
1423 'name' => 'group_id',
1424 'title' => ts('Group'),
1425 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
1426 'group' => TRUE,
16e2e80c 1427 'options' => CRM_Core_PseudoConstant::nestedGroup(),
6a488035
TO
1428 ),
1429 );
1430 if (empty($this->_columns['civicrm_group']['dao'])) {
1431 $this->_columns['civicrm_group']['dao'] = 'CRM_Contact_DAO_GroupContact';
1432 }
1433 if (empty($this->_columns['civicrm_group']['alias'])) {
1434 $this->_columns['civicrm_group']['alias'] = 'cgroup';
1435 }
1436 }
1437
74cf4551
EM
1438 /**
1439 * @param string $operator
1440 *
1441 * @return string
1442 */
00be9182 1443 public function getSQLOperator($operator = "like") {
6a488035
TO
1444 switch ($operator) {
1445 case 'eq':
1446 return '=';
1447
1448 case 'lt':
1449 return '<';
1450
1451 case 'lte':
1452 return '<=';
1453
1454 case 'gt':
1455 return '>';
1456
1457 case 'gte':
1458 return '>=';
1459
1460 case 'ne':
1461 case 'neq':
1462 return '!=';
1463
1464 case 'nhas':
1465 return 'NOT LIKE';
1466
1467 case 'in':
1468 return 'IN';
1469
1470 case 'notin':
1471 return 'NOT IN';
1472
1473 case 'nll':
1474 return 'IS NULL';
1475
1476 case 'nnll':
1477 return 'IS NOT NULL';
1478
1479 default:
1480 // type is string
1481 return 'LIKE';
1482 }
1483 }
1484
74cf4551 1485 /**
f587aa17 1486 * Generate where clause.
8a925f33 1487 * This can be overridden in reports for special treatment of a field
f587aa17 1488 *
537c70b8 1489 * @param array $field Field specifications
8a925f33 1490 * @param string $op Query operator (not an exact match to sql)
f587aa17
EM
1491 * @param mixed $value
1492 * @param float $min
1493 * @param float $max
74cf4551
EM
1494 *
1495 * @return null|string
1496 */
8a925f33 1497 public function whereClause(&$field, $op, $value, $min, $max) {
6a488035
TO
1498
1499 $type = CRM_Utils_Type::typeToString(CRM_Utils_Array::value('type', $field));
1500 $clause = NULL;
1501
1502 switch ($op) {
1503 case 'bw':
1504 case 'nbw':
1505 if (($min !== NULL && strlen($min) > 0) ||
1506 ($max !== NULL && strlen($max) > 0)
1507 ) {
9d72cede
EM
1508 $min = CRM_Utils_Type::escape($min, $type);
1509 $max = CRM_Utils_Type::escape($max, $type);
6a488035
TO
1510 $clauses = array();
1511 if ($min) {
1512 if ($op == 'bw') {
1513 $clauses[] = "( {$field['dbAlias']} >= $min )";
1514 }
1515 else {
1516 $clauses[] = "( {$field['dbAlias']} < $min )";
1517 }
1518 }
1519 if ($max) {
1520 if ($op == 'bw') {
1521 $clauses[] = "( {$field['dbAlias']} <= $max )";
1522 }
1523 else {
1524 $clauses[] = "( {$field['dbAlias']} > $max )";
1525 }
1526 }
1527
1528 if (!empty($clauses)) {
1529 if ($op == 'bw') {
1530 $clause = implode(' AND ', $clauses);
1531 }
1532 else {
1533 $clause = implode(' OR ', $clauses);
1534 }
1535 }
1536 }
1537 break;
1538
1539 case 'has':
1540 case 'nhas':
1541 if ($value !== NULL && strlen($value) > 0) {
1542 $value = CRM_Utils_Type::escape($value, $type);
1543 if (strpos($value, '%') === FALSE) {
1544 $value = "'%{$value}%'";
1545 }
1546 else {
1547 $value = "'{$value}'";
1548 }
29fc2b79 1549 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1550 $clause = "( {$field['dbAlias']} $sqlOP $value )";
1551 }
1552 break;
1553
1554 case 'in':
1555 case 'notin':
2107cde9
CW
1556 if (is_string($value) && strlen($value)) {
1557 $value = explode(',', $value);
1558 }
6a488035 1559 if ($value !== NULL && is_array($value) && count($value) > 0) {
29fc2b79 1560 $sqlOP = $this->getSQLOperator($op);
9d72cede
EM
1561 if (CRM_Utils_Array::value('type', $field) ==
1562 CRM_Utils_Type::T_STRING
1563 ) {
f587aa17 1564 //cycle through selections and escape values
6a488035
TO
1565 foreach ($value as $key => $selection) {
1566 $value[$key] = CRM_Utils_Type::escape($selection, $type);
1567 }
971d41b1
CW
1568 $clause
1569 = "( {$field['dbAlias']} $sqlOP ( '" . implode("' , '", $value) .
9d72cede 1570 "') )";
6a488035
TO
1571 }
1572 else {
1573 // for numerical values
9d72cede
EM
1574 $clause = "{$field['dbAlias']} $sqlOP (" . implode(', ', $value) .
1575 ")";
6a488035
TO
1576 }
1577 if ($op == 'notin') {
1578 $clause = "( " . $clause . " OR {$field['dbAlias']} IS NULL )";
1579 }
1580 else {
1581 $clause = "( " . $clause . " )";
1582 }
1583 }
1584 break;
1585
1586 case 'mhas':
1587 // mhas == multiple has
1588 if ($value !== NULL && count($value) > 0) {
29fc2b79 1589 $sqlOP = $this->getSQLOperator($op);
971d41b1
CW
1590 $clause
1591 = "{$field['dbAlias']} REGEXP '[[:<:]]" . implode('|', $value) .
9d72cede 1592 "[[:>:]]'";
6a488035
TO
1593 }
1594 break;
77b97be7 1595
67788963
J
1596 case 'mnot':
1597 // mnot == multiple is not one of
1598 if ($value !== NULL && count($value) > 0) {
1599 $sqlOP = $this->getSQLOperator($op);
971d41b1
CW
1600 $clause
1601 = "( {$field['dbAlias']} NOT REGEXP '[[:<:]]" . implode('|', $value) .
9d72cede 1602 "[[:>:]]' OR {$field['dbAlias']} IS NULL )";
67788963
J
1603 }
1604 break;
6a488035
TO
1605
1606 case 'sw':
1607 case 'ew':
1608 if ($value !== NULL && strlen($value) > 0) {
1609 $value = CRM_Utils_Type::escape($value, $type);
1610 if (strpos($value, '%') === FALSE) {
1611 if ($op == 'sw') {
1612 $value = "'{$value}%'";
1613 }
1614 else {
1615 $value = "'%{$value}'";
1616 }
1617 }
1618 else {
1619 $value = "'{$value}'";
1620 }
29fc2b79 1621 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1622 $clause = "( {$field['dbAlias']} $sqlOP $value )";
1623 }
1624 break;
1625
1626 case 'nll':
1627 case 'nnll':
29fc2b79 1628 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1629 $clause = "( {$field['dbAlias']} $sqlOP )";
1630 break;
1631
1632 default:
1633 if ($value !== NULL && strlen($value) > 0) {
1634 if (isset($field['clause'])) {
1635 // FIXME: we not doing escape here. Better solution is to use two
1636 // different types - data-type and filter-type
0e6e8724 1637 $clause = $field['clause'];
6a488035
TO
1638 }
1639 else {
1640 $value = CRM_Utils_Type::escape($value, $type);
29fc2b79 1641 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1642 if ($field['type'] == CRM_Utils_Type::T_STRING) {
1643 $value = "'{$value}'";
1644 }
1645 $clause = "( {$field['dbAlias']} $sqlOP $value )";
1646 }
1647 }
1648 break;
1649 }
1650
a7488080 1651 if (!empty($field['group']) && $clause) {
6a488035
TO
1652 $clause = $this->whereGroupClause($field, $value, $op);
1653 }
a7488080 1654 elseif (!empty($field['tag']) && $clause) {
6a488035
TO
1655 // not using left join in query because if any contact
1656 // belongs to more than one tag, results duplicate
1657 // entries.
1658 $clause = $this->whereTagClause($field, $value, $op);
1659 }
114a2c85 1660 elseif (!empty($field['membership_org']) && $clause) {
f587aa17 1661 $clause = $this->whereMembershipOrgClause($value, $op);
114a2c85
DG
1662 }
1663 elseif (!empty($field['membership_type']) && $clause) {
f587aa17 1664 $clause = $this->whereMembershipTypeClause($value, $op);
114a2c85 1665 }
6a488035
TO
1666 return $clause;
1667 }
1668
74cf4551 1669 /**
100fef9d 1670 * @param string $fieldName
74cf4551 1671 * @param $relative
f587aa17
EM
1672 * @param string $from
1673 * @param string $to
74cf4551
EM
1674 * @param null $type
1675 * @param null $fromTime
1676 * @param null $toTime
1677 *
1678 * @return null|string
1679 */
971d41b1 1680 public function dateClause(
7d8c1168
TO
1681 $fieldName,
1682 $relative, $from, $to, $type = NULL, $fromTime = NULL, $toTime = NULL
6a488035
TO
1683 ) {
1684 $clauses = array();
160d32e1 1685 if (in_array($relative, array_keys($this->getOperationPair(CRM_Report_Form::OP_DATE)))) {
29fc2b79 1686 $sqlOP = $this->getSQLOperator($relative);
6a488035
TO
1687 return "( {$fieldName} {$sqlOP} )";
1688 }
1689
29fc2b79 1690 list($from, $to) = $this->getFromTo($relative, $from, $to, $fromTime, $toTime);
6a488035
TO
1691
1692 if ($from) {
1693 $from = ($type == CRM_Utils_Type::T_DATE) ? substr($from, 0, 8) : $from;
1694 $clauses[] = "( {$fieldName} >= $from )";
1695 }
1696
1697 if ($to) {
1698 $to = ($type == CRM_Utils_Type::T_DATE) ? substr($to, 0, 8) : $to;
1699 $clauses[] = "( {$fieldName} <= {$to} )";
1700 }
1701
1702 if (!empty($clauses)) {
1703 return implode(' AND ', $clauses);
1704 }
1705
1706 return NULL;
1707 }
9d72cede
EM
1708
1709 /**
1710 * @todo - could not find any instances where this is called
1711 *
537c70b8 1712 * @param bool $relative
7e06c9f5
TO
1713 * @param string $from
1714 * @param string_type $to
9d72cede
EM
1715 *
1716 * @return string|NULL
1717 */
00be9182 1718 public function dateDisplay($relative, $from, $to) {
29fc2b79 1719 list($from, $to) = $this->getFromTo($relative, $from, $to);
6a488035
TO
1720
1721 if ($from) {
1722 $clauses[] = CRM_Utils_Date::customFormat($from, NULL, array('m', 'M'));
1723 }
1724 else {
1725 $clauses[] = 'Past';
1726 }
1727
1728 if ($to) {
1729 $clauses[] = CRM_Utils_Date::customFormat($to, NULL, array('m', 'M'));
1730 }
1731 else {
1732 $clauses[] = 'Today';
1733 }
1734
1735 if (!empty($clauses)) {
1736 return implode(' - ', $clauses);
1737 }
1738
1739 return NULL;
1740 }
1741
74cf4551 1742 /**
f587aa17
EM
1743 * @param bool $relative
1744 * @param string $from
1745 * @param string $to
9907633b
EM
1746 * @param string $fromTime
1747 * @param string $toTime
74cf4551
EM
1748 *
1749 * @return array
1750 */
9907633b
EM
1751 public function getFromTo($relative, $from, $to, $fromTime = NULL, $toTime = NULL) {
1752 if (empty($toTime)) {
1753 $toTime = '235959';
6a488035
TO
1754 }
1755 //FIX ME not working for relative
1756 if ($relative) {
1757 list($term, $unit) = CRM_Utils_System::explode('.', $relative, 2);
1758 $dateRange = CRM_Utils_Date::relativeToAbsolute($term, $unit);
1759 $from = substr($dateRange['from'], 0, 8);
1760 //Take only Date Part, Sometime Time part is also present in 'to'
1761 $to = substr($dateRange['to'], 0, 8);
1762 }
9907633b
EM
1763 $from = CRM_Utils_Date::processDate($from, $fromTime);
1764 $to = CRM_Utils_Date::processDate($to, $toTime);
6a488035
TO
1765 return array($from, $to);
1766 }
1767
74cf4551 1768 /**
4b62bc4f
EM
1769 * Alter display of rows.
1770 *
1771 * Iterate through the rows retrieved via SQL and make changes for display purposes,
1772 * such as rendering contacts as links.
1773 *
1774 * @param array $rows
1775 * Rows generated by SQL, with an array for each row.
74cf4551 1776 */
00be9182 1777 public function alterDisplay(&$rows) {
6a488035
TO
1778 }
1779
74cf4551
EM
1780 /**
1781 * @param $rows
1782 */
00be9182 1783 public function alterCustomDataDisplay(&$rows) {
6a488035
TO
1784 // custom code to alter rows having custom values
1785 if (empty($this->_customGroupExtends)) {
1786 return;
1787 }
1788
1789 $customFieldIds = array();
1790 foreach ($this->_params['fields'] as $fieldAlias => $value) {
1791 if ($fieldId = CRM_Core_BAO_CustomField::getKeyID($fieldAlias)) {
1792 $customFieldIds[$fieldAlias] = $fieldId;
1793 }
1794 }
1795 if (empty($customFieldIds)) {
1796 return;
1797 }
1798
1799 $customFields = $fieldValueMap = array();
9d72cede
EM
1800 $customFieldCols = array(
1801 'column_name',
1802 'data_type',
1803 'html_type',
1804 'option_group_id',
21dfd5f5 1805 'id',
9d72cede 1806 );
6a488035
TO
1807
1808 // skip for type date and ContactReference since date format is already handled
1809 $query = "
1810SELECT cg.table_name, cf." . implode(", cf.", $customFieldCols) . ", ov.value, ov.label
1811FROM civicrm_custom_field cf
1812INNER JOIN civicrm_custom_group cg ON cg.id = cf.custom_group_id
1813LEFT JOIN civicrm_option_value ov ON cf.option_group_id = ov.option_group_id
1814WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
1815 cg.is_active = 1 AND
1816 cf.is_active = 1 AND
1817 cf.is_searchable = 1 AND
1818 cf.data_type NOT IN ('ContactReference', 'Date') AND
1819 cf.id IN (" . implode(",", $customFieldIds) . ")";
1820
1821 $dao = CRM_Core_DAO::executeQuery($query);
1822 while ($dao->fetch()) {
1823 foreach ($customFieldCols as $key) {
9d72cede
EM
1824 $customFields[$dao->table_name . '_custom_' .
1825 $dao->id][$key] = $dao->$key;
6a488035
TO
1826 }
1827 if ($dao->option_group_id) {
1828 $fieldValueMap[$dao->option_group_id][$dao->value] = $dao->label;
1829 }
1830 }
1831 $dao->free();
1832
1833 $entryFound = FALSE;
1834 foreach ($rows as $rowNum => $row) {
1835 foreach ($row as $tableCol => $val) {
1836 if (array_key_exists($tableCol, $customFields)) {
1837 $rows[$rowNum][$tableCol] = $this->formatCustomValues($val, $customFields[$tableCol], $fieldValueMap);
1838 $entryFound = TRUE;
1839 }
1840 }
1841
1842 // skip looking further in rows, if first row itself doesn't
1843 // have the column we need
1844 if (!$entryFound) {
1845 break;
1846 }
1847 }
1848 }
1849
74cf4551
EM
1850 /**
1851 * @param $value
1852 * @param $customField
1853 * @param $fieldValueMap
1854 *
971d41b1 1855 * @return float|string|void
74cf4551 1856 */
00be9182 1857 public function formatCustomValues($value, $customField, $fieldValueMap) {
6a488035 1858 if (CRM_Utils_System::isNull($value)) {
bed98343 1859 return NULL;
6a488035
TO
1860 }
1861
1862 $htmlType = $customField['html_type'];
1863
1864 switch ($customField['data_type']) {
1865 case 'Boolean':
1866 if ($value == '1') {
1867 $retValue = ts('Yes');
1868 }
1869 else {
1870 $retValue = ts('No');
1871 }
1872 break;
1873
1874 case 'Link':
1875 $retValue = CRM_Utils_System::formatWikiURL($value);
1876 break;
1877
1878 case 'File':
1879 $retValue = $value;
1880 break;
1881
1882 case 'Memo':
1883 $retValue = $value;
1884 break;
1885
1886 case 'Float':
1887 if ($htmlType == 'Text') {
9d72cede 1888 $retValue = (float) $value;
6a488035
TO
1889 break;
1890 }
1891 case 'Money':
1892 if ($htmlType == 'Text') {
6a488035
TO
1893 $retValue = CRM_Utils_Money::format($value, NULL, '%a');
1894 break;
1895 }
1896 case 'String':
1897 case 'Int':
1898 if (in_array($htmlType, array(
9d72cede 1899 'Text',
21dfd5f5 1900 'TextArea',
9d72cede 1901 ))) {
6a488035
TO
1902 $retValue = $value;
1903 break;
1904 }
1905 case 'StateProvince':
1906 case 'Country':
1907
1908 switch ($htmlType) {
1909 case 'Multi-Select Country':
1910 $value = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
1911 $customData = array();
1912 foreach ($value as $val) {
1913 if ($val) {
1914 $customData[] = CRM_Core_PseudoConstant::country($val, FALSE);
1915 }
1916 }
1917 $retValue = implode(', ', $customData);
1918 break;
1919
1920 case 'Select Country':
1921 $retValue = CRM_Core_PseudoConstant::country($value, FALSE);
1922 break;
1923
1924 case 'Select State/Province':
1925 $retValue = CRM_Core_PseudoConstant::stateProvince($value, FALSE);
1926 break;
1927
1928 case 'Multi-Select State/Province':
1929 $value = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
1930 $customData = array();
1931 foreach ($value as $val) {
1932 if ($val) {
1933 $customData[] = CRM_Core_PseudoConstant::stateProvince($val, FALSE);
1934 }
1935 }
1936 $retValue = implode(', ', $customData);
1937 break;
1938
1939 case 'Select':
1940 case 'Radio':
1941 case 'Autocomplete-Select':
1942 $retValue = $fieldValueMap[$customField['option_group_id']][$value];
1943 break;
1944
1945 case 'CheckBox':
1946 case 'AdvMulti-Select':
1947 case 'Multi-Select':
1948 $value = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
1949 $customData = array();
1950 foreach ($value as $val) {
1951 if ($val) {
1952 $customData[] = $fieldValueMap[$customField['option_group_id']][$val];
1953 }
1954 }
1955 $retValue = implode(', ', $customData);
1956 break;
1957
1958 default:
1959 $retValue = $value;
1960 }
1961 break;
1962
1963 default:
1964 $retValue = $value;
1965 }
1966
1967 return $retValue;
1968 }
1969
74cf4551
EM
1970 /**
1971 * @param $rows
1972 */
00be9182 1973 public function removeDuplicates(&$rows) {
6a488035
TO
1974 if (empty($this->_noRepeats)) {
1975 return;
1976 }
1977 $checkList = array();
1978
1979 foreach ($rows as $key => $list) {
1980 foreach ($list as $colName => $colVal) {
8f1445ea 1981 if (array_key_exists($colName, $checkList) &&
9d72cede
EM
1982 $checkList[$colName] == $colVal
1983 ) {
6a488035
TO
1984 $rows[$key][$colName] = "";
1985 }
1986 if (in_array($colName, $this->_noRepeats)) {
1987 $checkList[$colName] = $colVal;
1988 }
1989 }
1990 }
1991 }
1992
74cf4551
EM
1993 /**
1994 * @param $row
1995 * @param $fields
1996 * @param bool $subtotal
1997 */
00be9182 1998 public function fixSubTotalDisplay(&$row, $fields, $subtotal = TRUE) {
6a488035
TO
1999 foreach ($row as $colName => $colVal) {
2000 if (in_array($colName, $fields)) {
6a488035
TO
2001 }
2002 elseif (isset($this->_columnHeaders[$colName])) {
2003 if ($subtotal) {
2004 $row[$colName] = "Subtotal";
2005 $subtotal = FALSE;
2006 }
2007 else {
2008 unset($row[$colName]);
2009 }
2010 }
2011 }
2012 }
2013
74cf4551
EM
2014 /**
2015 * @param $rows
2016 *
2017 * @return bool
2018 */
00be9182 2019 public function grandTotal(&$rows) {
6a488035
TO
2020 if (!$this->_rollup || ($this->_rollup == '') ||
2021 ($this->_limit && count($rows) >= self::ROW_COUNT_LIMIT)
2022 ) {
2023 return FALSE;
2024 }
2025 $lastRow = array_pop($rows);
2026
6a488035
TO
2027 foreach ($this->_columnHeaders as $fld => $val) {
2028 if (!in_array($fld, $this->_statFields)) {
2029 if (!$this->_grandFlag) {
2030 $lastRow[$fld] = "Grand Total";
2031 $this->_grandFlag = TRUE;
2032 }
2033 else {
2034 $lastRow[$fld] = "";
2035 }
2036 }
2037 }
2038
2039 $this->assign('grandStat', $lastRow);
2040 return TRUE;
2041 }
2042
74cf4551
EM
2043 /**
2044 * @param $rows
2045 * @param bool $pager
2046 */
00be9182 2047 public function formatDisplay(&$rows, $pager = TRUE) {
6a488035
TO
2048 // set pager based on if any limit was applied in the query.
2049 if ($pager) {
2050 $this->setPager();
2051 }
2052
2053 // allow building charts if any
2054 if (!empty($this->_params['charts']) && !empty($rows)) {
2055 $this->buildChart($rows);
2056 $this->assign('chartEnabled', TRUE);
9d72cede
EM
2057 $this->_chartId = "{$this->_params['charts']}_" .
2058 ($this->_id ? $this->_id : substr(get_class($this), 16)) . '_' .
2059 session_id();
6a488035
TO
2060 $this->assign('chartId', $this->_chartId);
2061 }
2062
2063 // unset columns not to be displayed.
2064 foreach ($this->_columnHeaders as $key => $value) {
a7488080 2065 if (!empty($value['no_display'])) {
6a488035
TO
2066 unset($this->_columnHeaders[$key]);
2067 }
2068 }
2069
2070 // unset columns not to be displayed.
2071 if (!empty($rows)) {
2072 foreach ($this->_noDisplay as $noDisplayField) {
2073 foreach ($rows as $rowNum => $row) {
2074 unset($this->_columnHeaders[$noDisplayField]);
2075 }
2076 }
2077 }
2078
2079 // build array of section totals
2080 $this->sectionTotals();
2081
2082 // process grand-total row
2083 $this->grandTotal($rows);
2084
2085 // use this method for formatting rows for display purpose.
2086 $this->alterDisplay($rows);
2087 CRM_Utils_Hook::alterReportVar('rows', $rows, $this);
2088
2089 // use this method for formatting custom rows for display purpose.
2090 $this->alterCustomDataDisplay($rows);
2091 }
2092
74cf4551
EM
2093 /**
2094 * @param $rows
2095 */
00be9182 2096 public function buildChart(&$rows) {
6a488035
TO
2097 // override this method for building charts.
2098 }
2099
2100 // select() method below has been added recently (v3.3), and many of the report templates might
2101 // still be having their own select() method. We should fix them as and when encountered and move
2102 // towards generalizing the select() method below.
971d41b1
CW
2103
2104 /**
2105 * Generate the SELECT clause and set class variable $_select
2106 */
00be9182 2107 public function select() {
1f220d30 2108 $select = $this->_selectAliases = array();
6a488035
TO
2109
2110 foreach ($this->_columns as $tableName => $table) {
2111 if (array_key_exists('fields', $table)) {
2112 foreach ($table['fields'] as $fieldName => $field) {
2113 if ($tableName == 'civicrm_address') {
2114 $this->_addressField = TRUE;
2115 }
2116 if ($tableName == 'civicrm_email') {
2117 $this->_emailField = TRUE;
2118 }
2119 if ($tableName == 'civicrm_phone') {
2120 $this->_phoneField = TRUE;
2121 }
2122
9d72cede
EM
2123 if (!empty($field['required']) ||
2124 !empty($this->_params['fields'][$fieldName])
2125 ) {
6a488035
TO
2126
2127 // 1. In many cases we want select clause to be built in slightly different way
2128 // for a particular field of a particular type.
2129 // 2. This method when used should receive params by reference and modify $this->_columnHeaders
2130 // as needed.
2131 $selectClause = $this->selectClause($tableName, 'fields', $fieldName, $field);
2132 if ($selectClause) {
2133 $select[] = $selectClause;
2134 continue;
2135 }
2136
2137 // include statistics columns only if set
a7488080 2138 if (!empty($field['statistics'])) {
6a488035
TO
2139 foreach ($field['statistics'] as $stat => $label) {
2140 $alias = "{$tableName}_{$fieldName}_{$stat}";
2141 switch (strtolower($stat)) {
2142 case 'max':
2143 case 'sum':
2144 $select[] = "$stat({$field['dbAlias']}) as $alias";
2145 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
2146 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
9e6d7767 2147 $this->_statFields[$label] = $alias;
6a488035
TO
2148 $this->_selectAliases[] = $alias;
2149 break;
2150
2151 case 'count':
2152 $select[] = "COUNT({$field['dbAlias']}) as $alias";
2153 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
2154 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = CRM_Utils_Type::T_INT;
9e6d7767 2155 $this->_statFields[$label] = $alias;
6a488035
TO
2156 $this->_selectAliases[] = $alias;
2157 break;
2158
1bfaf6a6
DS
2159 case 'count_distinct':
2160 $select[] = "COUNT(DISTINCT {$field['dbAlias']}) as $alias";
2161 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
2162 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = CRM_Utils_Type::T_INT;
2163 $this->_statFields[$label] = $alias;
2164 $this->_selectAliases[] = $alias;
2165 break;
2166
6a488035
TO
2167 case 'avg':
2168 $select[] = "ROUND(AVG({$field['dbAlias']}),2) as $alias";
2169 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
2170 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
9e6d7767 2171 $this->_statFields[$label] = $alias;
6a488035
TO
2172 $this->_selectAliases[] = $alias;
2173 break;
2174 }
2175 }
2176 }
2177 else {
2178 $alias = "{$tableName}_{$fieldName}";
2179 $select[] = "{$field['dbAlias']} as $alias";
2180 $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = CRM_Utils_Array::value('title', $field);
2181 $this->_columnHeaders["{$tableName}_{$fieldName}"]['type'] = CRM_Utils_Array::value('type', $field);
2182 $this->_selectAliases[] = $alias;
2183 }
2184 }
2185 }
2186 }
2187
2188 // select for group bys
2189 if (array_key_exists('group_bys', $table)) {
2190 foreach ($table['group_bys'] as $fieldName => $field) {
2191
2192 if ($tableName == 'civicrm_address') {
2193 $this->_addressField = TRUE;
2194 }
2195 if ($tableName == 'civicrm_email') {
2196 $this->_emailField = TRUE;
2197 }
2198 if ($tableName == 'civicrm_phone') {
2199 $this->_phoneField = TRUE;
2200 }
2201 // 1. In many cases we want select clause to be built in slightly different way
2202 // for a particular field of a particular type.
2203 // 2. This method when used should receive params by reference and modify $this->_columnHeaders
2204 // as needed.
2205 $selectClause = $this->selectClause($tableName, 'group_bys', $fieldName, $field);
2206 if ($selectClause) {
2207 $select[] = $selectClause;
2208 continue;
2209 }
2210
9d72cede
EM
2211 if (!empty($this->_params['group_bys']) &&
2212 !empty($this->_params['group_bys'][$fieldName]) &&
2213 !empty($this->_params['group_bys_freq'])
2214 ) {
6a488035
TO
2215 switch (CRM_Utils_Array::value($fieldName, $this->_params['group_bys_freq'])) {
2216 case 'YEARWEEK':
9d72cede
EM
2217 $select[] = "DATE_SUB({$field['dbAlias']}, INTERVAL WEEKDAY({$field['dbAlias']}) DAY) AS {$tableName}_{$fieldName}_start";
2218 $select[] = "YEARWEEK({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2219 $select[] = "WEEKOFYEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2220 $field['title'] = 'Week';
2221 break;
2222
2223 case 'YEAR':
9d72cede
EM
2224 $select[] = "MAKEDATE(YEAR({$field['dbAlias']}), 1) AS {$tableName}_{$fieldName}_start";
2225 $select[] = "YEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2226 $select[] = "YEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2227 $field['title'] = 'Year';
2228 break;
2229
2230 case 'MONTH':
9d72cede
EM
2231 $select[] = "DATE_SUB({$field['dbAlias']}, INTERVAL (DAYOFMONTH({$field['dbAlias']})-1) DAY) as {$tableName}_{$fieldName}_start";
2232 $select[] = "MONTH({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2233 $select[] = "MONTHNAME({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2234 $field['title'] = 'Month';
2235 break;
2236
2237 case 'QUARTER':
9d72cede
EM
2238 $select[] = "STR_TO_DATE(CONCAT( 3 * QUARTER( {$field['dbAlias']} ) -2 , '/', '1', '/', YEAR( {$field['dbAlias']} ) ), '%m/%d/%Y') AS {$tableName}_{$fieldName}_start";
2239 $select[] = "QUARTER({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2240 $select[] = "QUARTER({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2241 $field['title'] = 'Quarter';
2242 break;
2243 }
2244 // for graphs and charts -
a7488080 2245 if (!empty($this->_params['group_bys_freq'][$fieldName])) {
6a488035 2246 $this->_interval = $field['title'];
971d41b1
CW
2247 $this->_columnHeaders["{$tableName}_{$fieldName}_start"]['title']
2248 = $field['title'] . ' Beginning';
6a488035
TO
2249 $this->_columnHeaders["{$tableName}_{$fieldName}_start"]['type'] = $field['type'];
2250 $this->_columnHeaders["{$tableName}_{$fieldName}_start"]['group_by'] = $this->_params['group_bys_freq'][$fieldName];
2251
2252 // just to make sure these values are transfered to rows.
2253 // since we 'll need them for calculation purpose,
2254 // e.g making subtotals look nicer or graphs
2255 $this->_columnHeaders["{$tableName}_{$fieldName}_interval"] = array('no_display' => TRUE);
2256 $this->_columnHeaders["{$tableName}_{$fieldName}_subtotal"] = array('no_display' => TRUE);
2257 }
2258 }
2259 }
2260 }
2261 }
2262
1f220d30 2263 $this->_selectClauses = $select;
6a488035
TO
2264 $this->_select = "SELECT " . implode(', ', $select) . " ";
2265 }
2266
74cf4551 2267 /**
100fef9d 2268 * @param string $tableName
74cf4551 2269 * @param $tableKey
100fef9d 2270 * @param string $fieldName
74cf4551
EM
2271 * @param $field
2272 *
2273 * @return bool
2274 */
00be9182 2275 public function selectClause(&$tableName, $tableKey, &$fieldName, &$field) {
6a488035
TO
2276 return FALSE;
2277 }
2278
00be9182 2279 public function where() {
adfe2750 2280 $this->storeWhereHavingClauseArray();
2281
2282 if (empty($this->_whereClauses)) {
2283 $this->_where = "WHERE ( 1 ) ";
2284 $this->_having = "";
2285 }
2286 else {
2287 $this->_where = "WHERE " . implode(' AND ', $this->_whereClauses);
2288 }
2289
2290 if ($this->_aclWhere) {
2291 $this->_where .= " AND {$this->_aclWhere} ";
2292 }
2293
2294 if (!empty($this->_havingClauses)) {
2295 // use this clause to construct group by clause.
2296 $this->_having = "HAVING " . implode(' AND ', $this->_havingClauses);
2297 }
2298 }
2299
2300 /**
2301 * Store Where clauses into an array - breaking out this step makes
2302 * over-riding more flexible as the clauses can be used in constructing a
2303 * temp table that may not be part of the final where clause or added
2304 * in other functions
2305 */
00be9182 2306 public function storeWhereHavingClauseArray() {
6a488035
TO
2307 foreach ($this->_columns as $tableName => $table) {
2308 if (array_key_exists('filters', $table)) {
2309 foreach ($table['filters'] as $fieldName => $field) {
d12de91c 2310 // respect pseudofield to filter spec so fields can be marked as
2311 // not to be handled here
9d72cede 2312 if (!empty($field['pseudofield'])) {
d12de91c 2313 continue;
2314 }
6a488035
TO
2315 $clause = NULL;
2316 if (CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_DATE) {
9d72cede
EM
2317 if (CRM_Utils_Array::value('operatorType', $field) ==
2318 CRM_Report_Form::OP_MONTH
2319 ) {
6a488035
TO
2320 $op = CRM_Utils_Array::value("{$fieldName}_op", $this->_params);
2321 $value = CRM_Utils_Array::value("{$fieldName}_value", $this->_params);
2322 if (is_array($value) && !empty($value)) {
971d41b1
CW
2323 $clause
2324 = "(month({$field['dbAlias']}) $op (" . implode(', ', $value) .
9d72cede 2325 '))';
6a488035
TO
2326 }
2327 }
2328 else {
2329 $relative = CRM_Utils_Array::value("{$fieldName}_relative", $this->_params);
9d72cede
EM
2330 $from = CRM_Utils_Array::value("{$fieldName}_from", $this->_params);
2331 $to = CRM_Utils_Array::value("{$fieldName}_to", $this->_params);
6a488035 2332 $fromTime = CRM_Utils_Array::value("{$fieldName}_from_time", $this->_params);
9d72cede
EM
2333 $toTime = CRM_Utils_Array::value("{$fieldName}_to_time", $this->_params);
2334 $clause = $this->dateClause($field['dbAlias'], $relative, $from, $to, $field['type'], $fromTime, $toTime);
6a488035
TO
2335 }
2336 }
2337 else {
2338 $op = CRM_Utils_Array::value("{$fieldName}_op", $this->_params);
2339 if ($op) {
2340 $clause = $this->whereClause($field,
adfe2750 2341 $op,
2342 CRM_Utils_Array::value("{$fieldName}_value", $this->_params),
2343 CRM_Utils_Array::value("{$fieldName}_min", $this->_params),
2344 CRM_Utils_Array::value("{$fieldName}_max", $this->_params)
6a488035
TO
2345 );
2346 }
2347 }
2348
2349 if (!empty($clause)) {
a7488080 2350 if (!empty($field['having'])) {
adfe2750 2351 $this->_havingClauses[] = $clause;
6a488035
TO
2352 }
2353 else {
adfe2750 2354 $this->_whereClauses[] = $clause;
6a488035
TO
2355 }
2356 }
2357 }
2358 }
2359 }
2360
6a488035 2361 }
9d72cede 2362
00be9182 2363 public function processReportMode() {
6a488035
TO
2364 $buttonName = $this->controller->getButtonName();
2365
884605ca
DL
2366 $output = CRM_Utils_Request::retrieve(
2367 'output',
2368 'String',
2369 CRM_Core_DAO::$_nullObject
6a488035
TO
2370 );
2371
971d41b1
CW
2372 $this->_sendmail
2373 = CRM_Utils_Request::retrieve(
884605ca
DL
2374 'sendmail',
2375 'Boolean',
2376 CRM_Core_DAO::$_nullObject
2377 );
6a488035
TO
2378
2379 $this->_absoluteUrl = FALSE;
2380 $printOnly = FALSE;
2381 $this->assign('printOnly', FALSE);
2382
9d72cede
EM
2383 if ($this->_printButtonName == $buttonName || $output == 'print' ||
2384 ($this->_sendmail && !$output)
2385 ) {
6a488035
TO
2386 $this->assign('printOnly', TRUE);
2387 $printOnly = TRUE;
2388 $this->assign('outputMode', 'print');
2389 $this->_outputMode = 'print';
2390 if ($this->_sendmail) {
2391 $this->_absoluteUrl = TRUE;
9d72cede 2392 }
6a488035
TO
2393 }
2394 elseif ($this->_pdfButtonName == $buttonName || $output == 'pdf') {
2395 $this->assign('printOnly', TRUE);
2396 $printOnly = TRUE;
2397 $this->assign('outputMode', 'pdf');
2398 $this->_outputMode = 'pdf';
2399 $this->_absoluteUrl = TRUE;
2400 }
2401 elseif ($this->_csvButtonName == $buttonName || $output == 'csv') {
2402 $this->assign('printOnly', TRUE);
2403 $printOnly = TRUE;
2404 $this->assign('outputMode', 'csv');
2405 $this->_outputMode = 'csv';
2406 $this->_absoluteUrl = TRUE;
2407 }
2408 elseif ($this->_groupButtonName == $buttonName || $output == 'group') {
2409 $this->assign('outputMode', 'group');
2410 $this->_outputMode = 'group';
2411 }
2412 elseif ($output == 'create_report' && $this->_criteriaForm) {
2413 $this->assign('outputMode', 'create_report');
2414 $this->_outputMode = 'create_report';
2415 }
2416 else {
2417 $this->assign('outputMode', 'html');
2418 $this->_outputMode = 'html';
2419 }
2420
2421 // Get today's date to include in printed reports
2422 if ($printOnly) {
2423 $reportDate = CRM_Utils_Date::customFormat(date('Y-m-d H:i'));
2424 $this->assign('reportDate', $reportDate);
2425 }
2426 }
2427
9657ccf2
EM
2428 /**
2429 * Post Processing function for Form (postProcessCommon should be used to set other variables from input as the api accesses that function)
2430 */
00be9182 2431 public function beginPostProcess() {
c58f66e0 2432 $this->setParams($this->controller->exportValues($this->_name));
6a488035
TO
2433
2434 if (empty($this->_params) &&
2435 $this->_force
2436 ) {
c58f66e0 2437 $this->setParams($this->_formValues);
6a488035
TO
2438 }
2439
2440 // hack to fix params when submitted from dashboard, CRM-8532
2441 // fields array is missing because form building etc is skipped
2442 // in dashboard mode for report
c58f66e0 2443 //@todo - this could be done in the dashboard no we have a setter
a7488080 2444 if (empty($this->_params['fields']) && !$this->_noFields) {
c58f66e0 2445 $this->setParams($this->_formValues);
6a488035
TO
2446 }
2447
2448 $this->_formValues = $this->_params;
2449 if (CRM_Core_Permission::check('administer Reports') &&
2450 isset($this->_id) &&
9d72cede
EM
2451 ($this->_instanceButtonName ==
2452 $this->controller->getButtonName() . '_save' ||
6a488035
TO
2453 $this->_chartButtonName == $this->controller->getButtonName()
2454 )
2455 ) {
2456 $this->assign('updateReportButton', TRUE);
2457 }
2458 $this->processReportMode();
c58f66e0
E
2459 $this->beginPostProcessCommon();
2460 }
2461
2462 /**
100fef9d 2463 * BeginPostProcess function run in both report mode and non-report mode (api)
c58f66e0 2464 */
00be9182 2465 public function beginPostProcessCommon() {
c58f66e0 2466
6a488035
TO
2467 }
2468
74cf4551
EM
2469 /**
2470 * @param bool $applyLimit
2471 *
2472 * @return string
2473 */
00be9182 2474 public function buildQuery($applyLimit = TRUE) {
6a488035
TO
2475 $this->select();
2476 $this->from();
2477 $this->customDataFrom();
2478 $this->where();
2479 $this->groupBy();
2480 $this->orderBy();
2481
2482 // order_by columns not selected for display need to be included in SELECT
2483 $unselectedSectionColumns = $this->unselectedSectionColumns();
2484 foreach ($unselectedSectionColumns as $alias => $section) {
2485 $this->_select .= ", {$section['dbAlias']} as {$alias}";
2486 }
2487
8cc574cf 2488 if ($applyLimit && empty($this->_params['charts'])) {
6a488035
TO
2489 $this->limit();
2490 }
2491 CRM_Utils_Hook::alterReportVar('sql', $this, $this);
2492
2493 $sql = "{$this->_select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy} {$this->_limit}";
2494 return $sql;
2495 }
2496
00be9182 2497 public function groupBy() {
6a488035 2498 $groupBys = array();
a7488080 2499 if (!empty($this->_params['group_bys']) &&
6a488035
TO
2500 is_array($this->_params['group_bys']) &&
2501 !empty($this->_params['group_bys'])
2502 ) {
2503 foreach ($this->_columns as $tableName => $table) {
2504 if (array_key_exists('group_bys', $table)) {
2505 foreach ($table['group_bys'] as $fieldName => $field) {
a7488080 2506 if (!empty($this->_params['group_bys'][$fieldName])) {
6a488035
TO
2507 $groupBys[] = $field['dbAlias'];
2508 }
2509 }
2510 }
2511 }
2512 }
2513
2514 if (!empty($groupBys)) {
2515 $this->_groupBy = "GROUP BY " . implode(', ', $groupBys);
2516 }
2517 }
2518
00be9182 2519 public function orderBy() {
9d72cede 2520 $this->_orderBy = "";
6a488035
TO
2521 $this->_sections = array();
2522 $this->storeOrderByArray();
9d72cede 2523 if (!empty($this->_orderByArray) && !$this->_rollup == 'WITH ROLLUP') {
6a488035
TO
2524 $this->_orderBy = "ORDER BY " . implode(', ', $this->_orderByArray);
2525 }
2526 $this->assign('sections', $this->_sections);
2527 }
f2947aea 2528
688d37c6 2529 /**
6a488035
TO
2530 * In some cases other functions want to know which fields are selected for ordering by
2531 * Separating this into a separate function allows it to be called separately from constructing
2532 * the order by clause
2533 */
00be9182 2534 public function storeOrderByArray() {
9d72cede 2535 $orderBys = array();
6a488035 2536
a7488080 2537 if (!empty($this->_params['order_bys']) &&
6a488035
TO
2538 is_array($this->_params['order_bys']) &&
2539 !empty($this->_params['order_bys'])
2540 ) {
2541
9907633b 2542 // Process order_bys in user-specified order
6a488035
TO
2543 foreach ($this->_params['order_bys'] as $orderBy) {
2544 $orderByField = array();
2545 foreach ($this->_columns as $tableName => $table) {
2546 if (array_key_exists('order_bys', $table)) {
2547 // For DAO columns defined in $this->_columns
2548 $fields = $table['order_bys'];
2549 }
2550 elseif (array_key_exists('extends', $table)) {
2551 // For custom fields referenced in $this->_customGroupExtends
1efec7ff 2552 $fields = CRM_Utils_Array::value('fields', $table, array());
6a488035 2553 }
6f993086
AH
2554 else {
2555 continue;
2556 }
6a488035
TO
2557 if (!empty($fields) && is_array($fields)) {
2558 foreach ($fields as $fieldName => $field) {
2559 if ($fieldName == $orderBy['column']) {
f2947aea 2560 $orderByField = array_merge($field, $orderBy);
6a488035
TO
2561 $orderByField['tplField'] = "{$tableName}_{$fieldName}";
2562 break 2;
2563 }
2564 }
2565 }
2566 }
2567
2568 if (!empty($orderByField)) {
f2947aea 2569 $this->_orderByFields[] = $orderByField;
6a488035
TO
2570 $orderBys[] = "{$orderByField['dbAlias']} {$orderBy['order']}";
2571
2572 // Record any section headers for assignment to the template
a7488080 2573 if (!empty($orderBy['section'])) {
b5801e1d 2574 $orderByField['pageBreak'] = CRM_Utils_Array::value('pageBreak', $orderBy);
6a488035
TO
2575 $this->_sections[$orderByField['tplField']] = $orderByField;
2576 }
2577 }
2578 }
2579 }
2580
2581 $this->_orderByArray = $orderBys;
2582
2583 $this->assign('sections', $this->_sections);
2584 }
2585
74cf4551
EM
2586 /**
2587 * @return array
2588 */
00be9182 2589 public function unselectedSectionColumns() {
6a488035
TO
2590 $selectColumns = array();
2591 foreach ($this->_columns as $tableName => $table) {
2592 if (array_key_exists('fields', $table)) {
2593 foreach ($table['fields'] as $fieldName => $field) {
9d72cede
EM
2594 if (!empty($field['required']) ||
2595 !empty($this->_params['fields'][$fieldName])
2596 ) {
6a488035
TO
2597
2598 $selectColumns["{$tableName}_{$fieldName}"] = 1;
2599 }
2600 }
2601 }
2602 }
f2947aea 2603
6a488035
TO
2604 if (is_array($this->_sections)) {
2605 return array_diff_key($this->_sections, $selectColumns);
2606 }
2607 else {
2608 return array();
2609 }
2610 }
2611
74cf4551
EM
2612 /**
2613 * @param $sql
2614 * @param $rows
2615 */
00be9182 2616 public function buildRows($sql, &$rows) {
6a488035
TO
2617 $dao = CRM_Core_DAO::executeQuery($sql);
2618 if (!is_array($rows)) {
2619 $rows = array();
2620 }
2621
2622 // use this method to modify $this->_columnHeaders
2623 $this->modifyColumnHeaders();
2624
2625 $unselectedSectionColumns = $this->unselectedSectionColumns();
2626
2627 while ($dao->fetch()) {
2628 $row = array();
2629 foreach ($this->_columnHeaders as $key => $value) {
2630 if (property_exists($dao, $key)) {
2631 $row[$key] = $dao->$key;
2632 }
2633 }
2634
2635 // section headers not selected for display need to be added to row
2636 foreach ($unselectedSectionColumns as $key => $values) {
2637 if (property_exists($dao, $key)) {
2638 $row[$key] = $dao->$key;
2639 }
2640 }
2641
2642 $rows[] = $row;
2643 }
2644 }
2645
2646 /**
2647 * When "order by" fields are marked as sections, this assigns to the template
2648 * an array of total counts for each section. This data is used by the Smarty
2649 * plugin {sectionTotal}
2650 */
00be9182 2651 public function sectionTotals() {
6a488035
TO
2652
2653 // Reports using order_bys with sections must populate $this->_selectAliases in select() method.
2654 if (empty($this->_selectAliases)) {
2655 return;
2656 }
2657
2658 if (!empty($this->_sections)) {
2659 // build the query with no LIMIT clause
2660 $select = str_ireplace('SELECT SQL_CALC_FOUND_ROWS ', 'SELECT ', $this->_select);
2661 $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy}";
2662
2663 // pull section aliases out of $this->_sections
2664 $sectionAliases = array_keys($this->_sections);
2665
2666 $ifnulls = array();
2667 foreach (array_merge($sectionAliases, $this->_selectAliases) as $alias) {
2668 $ifnulls[] = "ifnull($alias, '') as $alias";
2669 }
2670
971d41b1
CW
2671 // Group (un-limited) report by all aliases and get counts. This might
2672 // be done more efficiently when the contents of $sql are known, ie. by
2673 // overriding this method in the report class.
6a488035 2674
9d72cede
EM
2675 $query = "select " . implode(", ", $ifnulls) .
2676 ", count(*) as ct from ($sql) as subquery group by " .
2677 implode(", ", $sectionAliases);
6a488035
TO
2678
2679 // initialize array of total counts
2680 $totals = array();
2681 $dao = CRM_Core_DAO::executeQuery($query);
2682 while ($dao->fetch()) {
2683
2684 // let $this->_alterDisplay translate any integer ids to human-readable values.
2685 $rows[0] = $dao->toArray();
2686 $this->alterDisplay($rows);
2687 $row = $rows[0];
2688
2689 // add totals for all permutations of section values
9d72cede
EM
2690 $values = array();
2691 $i = 1;
6a488035
TO
2692 $aliasCount = count($sectionAliases);
2693 foreach ($sectionAliases as $alias) {
2694 $values[] = $row[$alias];
2695 $key = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values);
2696 if ($i == $aliasCount) {
2697 // the last alias is the lowest-level section header; use count as-is
2698 $totals[$key] = $dao->ct;
2699 }
2700 else {
2701 // other aliases are higher level; roll count into their total
2702 $totals[$key] += $dao->ct;
2703 }
2704 }
2705 }
2706 $this->assign('sectionTotals', $totals);
2707 }
2708 }
2709
00be9182 2710 public function modifyColumnHeaders() {
6a488035
TO
2711 // use this method to modify $this->_columnHeaders
2712 }
2713
74cf4551
EM
2714 /**
2715 * @param $rows
2716 */
00be9182 2717 public function doTemplateAssignment(&$rows) {
6a488035
TO
2718 $this->assign_by_ref('columnHeaders', $this->_columnHeaders);
2719 $this->assign_by_ref('rows', $rows);
2720 $this->assign('statistics', $this->statistics($rows));
2721 }
2722
74cf4551 2723 /**
4f1f1f2a 2724 * override this method to build your own statistics
74cf4551
EM
2725 * @param $rows
2726 *
2727 * @return array
2728 */
00be9182 2729 public function statistics(&$rows) {
6a488035
TO
2730 $statistics = array();
2731
2732 $count = count($rows);
2733
2734 if ($this->_rollup && ($this->_rollup != '') && $this->_grandFlag) {
2735 $count++;
2736 }
2737
2738 $this->countStat($statistics, $count);
2739
2740 $this->groupByStat($statistics);
2741
2742 $this->filterStat($statistics);
2743
2744 return $statistics;
2745 }
2746
74cf4551
EM
2747 /**
2748 * @param $statistics
2749 * @param $count
2750 */
00be9182 2751 public function countStat(&$statistics, $count) {
9d72cede
EM
2752 $statistics['counts']['rowCount'] = array(
2753 'title' => ts('Row(s) Listed'),
2754 'value' => $count,
6a488035
TO
2755 );
2756
2757 if ($this->_rowsFound && ($this->_rowsFound > $count)) {
9d72cede
EM
2758 $statistics['counts']['rowsFound'] = array(
2759 'title' => ts('Total Row(s)'),
2760 'value' => $this->_rowsFound,
6a488035
TO
2761 );
2762 }
2763 }
2764
74cf4551
EM
2765 /**
2766 * @param $statistics
2767 */
00be9182 2768 public function groupByStat(&$statistics) {
a7488080 2769 if (!empty($this->_params['group_bys']) &&
6a488035
TO
2770 is_array($this->_params['group_bys']) &&
2771 !empty($this->_params['group_bys'])
2772 ) {
2773 foreach ($this->_columns as $tableName => $table) {
2774 if (array_key_exists('group_bys', $table)) {
2775 foreach ($table['group_bys'] as $fieldName => $field) {
a7488080 2776 if (!empty($this->_params['group_bys'][$fieldName])) {
6a488035
TO
2777 $combinations[] = $field['title'];
2778 }
2779 }
2780 }
2781 }
9d72cede
EM
2782 $statistics['groups'][] = array(
2783 'title' => ts('Grouping(s)'),
2784 'value' => implode(' & ', $combinations),
6a488035
TO
2785 );
2786 }
2787 }
2788
74cf4551
EM
2789 /**
2790 * @param $statistics
2791 */
00be9182 2792 public function filterStat(&$statistics) {
6a488035
TO
2793 foreach ($this->_columns as $tableName => $table) {
2794 if (array_key_exists('filters', $table)) {
2795 foreach ($table['filters'] as $fieldName => $field) {
9d72cede
EM
2796 if (CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_DATE &&
2797 CRM_Utils_Array::value('operatorType', $field) !=
2798 CRM_Report_Form::OP_MONTH
2799 ) {
971d41b1
CW
2800 list($from, $to)
2801 = $this->getFromTo(
8f1445ea
DL
2802 CRM_Utils_Array::value("{$fieldName}_relative", $this->_params),
2803 CRM_Utils_Array::value("{$fieldName}_from", $this->_params),
2804 CRM_Utils_Array::value("{$fieldName}_to", $this->_params),
2805 CRM_Utils_Array::value("{$fieldName}_from_time", $this->_params),
2806 CRM_Utils_Array::value("{$fieldName}_to_time", $this->_params)
2807 );
0d8afee2 2808 $from_time_format = !empty($this->_params["{$fieldName}_from_time"]) ? 'h' : 'd';
9d72cede 2809 $from = CRM_Utils_Date::customFormat($from, NULL, array($from_time_format));
6a488035 2810
0d8afee2 2811 $to_time_format = !empty($this->_params["{$fieldName}_to_time"]) ? 'h' : 'd';
9d72cede 2812 $to = CRM_Utils_Date::customFormat($to, NULL, array($to_time_format));
6a488035
TO
2813
2814 if ($from || $to) {
2815 $statistics['filters'][] = array(
2816 'title' => $field['title'],
10a5be27 2817 'value' => ts("Between %1 and %2", array(1 => $from, 2 => $to)),
6a488035
TO
2818 );
2819 }
2820 elseif (in_array($rel = CRM_Utils_Array::value("{$fieldName}_relative", $this->_params),
9d72cede
EM
2821 array_keys($this->getOperationPair(CRM_Report_Form::OP_DATE))
2822 )) {
160d32e1 2823 $pair = $this->getOperationPair(CRM_Report_Form::OP_DATE);
6a488035
TO
2824 $statistics['filters'][] = array(
2825 'title' => $field['title'],
2826 'value' => $pair[$rel],
2827 );
2828 }
2829 }
2830 else {
2831 $op = CRM_Utils_Array::value("{$fieldName}_op", $this->_params);
2832 $value = NULL;
2833 if ($op) {
1b36206c 2834 $pair = $this->getOperationPair(
8f1445ea
DL
2835 CRM_Utils_Array::value('operatorType', $field),
2836 $fieldName
6a488035
TO
2837 );
2838 $min = CRM_Utils_Array::value("{$fieldName}_min", $this->_params);
2839 $max = CRM_Utils_Array::value("{$fieldName}_max", $this->_params);
2840 $val = CRM_Utils_Array::value("{$fieldName}_value", $this->_params);
2107cde9
CW
2841 if (in_array($op, array('bw', 'nbw')) && ($min || $max)) {
2842 $value = "{$pair[$op]} $min " . ts('and') . " $max";
2843 }
d3e86119 2844 elseif ($val && CRM_Utils_Array::value('operatorType', $field) & self::OP_ENTITYREF) {
2107cde9 2845 $this->setEntityRefDefaults($field, $tableName);
9d72cede
EM
2846 $result = civicrm_api3($field['attributes']['entity'], 'getlist',
2847 array('id' => $val) +
2848 CRM_Utils_Array::value('api', $field['attributes'], array()));
2107cde9
CW
2849 $values = array();
2850 foreach ($result['values'] as $v) {
2851 $values[] = $v['label'];
2852 }
2853 $value = "{$pair[$op]} " . implode(', ', $values);
6a488035
TO
2854 }
2855 elseif ($op == 'nll' || $op == 'nnll') {
2856 $value = $pair[$op];
2857 }
2858 elseif (is_array($val) && (!empty($val))) {
f974e915 2859 $options = CRM_Utils_Array::value('options', $field, array());
6a488035
TO
2860 foreach ($val as $key => $valIds) {
2861 if (isset($options[$valIds])) {
2862 $val[$key] = $options[$valIds];
2863 }
2864 }
9d72cede
EM
2865 $pair[$op] = (count($val) == 1) ? (($op == 'notin' || $op ==
2866 'mnot') ? ts('Is Not') : ts('Is')) : CRM_Utils_Array::value($op, $pair);
2867 $val = implode(', ', $val);
2868 $value = "{$pair[$op]} " . $val;
6a488035 2869 }
9d72cede
EM
2870 elseif (!is_array($val) && (!empty($val) || $val == '0') &&
2871 isset($field['options']) &&
6a488035
TO
2872 is_array($field['options']) && !empty($field['options'])
2873 ) {
9d72cede
EM
2874 $value = CRM_Utils_Array::value($op, $pair) . " " .
2875 CRM_Utils_Array::value($val, $field['options'], $val);
6a488035
TO
2876 }
2877 elseif ($val) {
2878 $value = CRM_Utils_Array::value($op, $pair) . " " . $val;
2879 }
2880 }
2881 if ($value) {
9d72cede
EM
2882 $statistics['filters'][] = array(
2883 'title' => CRM_Utils_Array::value('title', $field),
2884 'value' => $value,
6a488035
TO
2885 );
2886 }
2887 }
2888 }
2889 }
2890 }
2891 }
2892
74cf4551
EM
2893 /**
2894 * @param null $rows
2895 */
00be9182 2896 public function endPostProcess(&$rows = NULL) {
9d72cede 2897 if ($this->_storeResultSet) {
ae555e90
DS
2898 $this->_resultSet = $rows;
2899 }
2900
6a488035
TO
2901 if ($this->_outputMode == 'print' ||
2902 $this->_outputMode == 'pdf' ||
2903 $this->_sendmail
2904 ) {
2905
2906 $content = $this->compileContent();
2907 $url = CRM_Utils_System::url("civicrm/report/instance/{$this->_id}",
9d72cede 2908 "reset=1", TRUE
6a488035
TO
2909 );
2910
2911 if ($this->_sendmail) {
2912 $config = CRM_Core_Config::singleton();
2913 $attachments = array();
2914
2915 if ($this->_outputMode == 'csv') {
971d41b1
CW
2916 $content
2917 = $this->_formValues['report_header'] . '<p>' . ts('Report URL') .
9d72cede
EM
2918 ": {$url}</p>" . '<p>' .
2919 ts('The report is attached as a CSV file.') . '</p>' .
2920 $this->_formValues['report_footer'];
2921
2922 $csvFullFilename = $config->templateCompileDir .
2923 CRM_Utils_File::makeFileName('CiviReport.csv');
6a488035
TO
2924 $csvContent = CRM_Report_Utils_Report::makeCsv($this, $rows);
2925 file_put_contents($csvFullFilename, $csvContent);
2926 $attachments[] = array(
2927 'fullPath' => $csvFullFilename,
2928 'mime_type' => 'text/csv',
2929 'cleanName' => 'CiviReport.csv',
2930 );
2931 }
2932 if ($this->_outputMode == 'pdf') {
2933 // generate PDF content
9d72cede
EM
2934 $pdfFullFilename = $config->templateCompileDir .
2935 CRM_Utils_File::makeFileName('CiviReport.pdf');
6a488035
TO
2936 file_put_contents($pdfFullFilename,
2937 CRM_Utils_PDF_Utils::html2pdf($content, "CiviReport.pdf",
2938 TRUE, array('orientation' => 'landscape')
2939 )
2940 );
2941 // generate Email Content
971d41b1
CW
2942 $content
2943 = $this->_formValues['report_header'] . '<p>' . ts('Report URL') .
9d72cede
EM
2944 ": {$url}</p>" . '<p>' .
2945 ts('The report is attached as a PDF file.') . '</p>' .
2946 $this->_formValues['report_footer'];
6a488035
TO
2947
2948 $attachments[] = array(
2949 'fullPath' => $pdfFullFilename,
2950 'mime_type' => 'application/pdf',
2951 'cleanName' => 'CiviReport.pdf',
2952 );
2953 }
2954
2955 if (CRM_Report_Utils_Report::mailReport($content, $this->_id,
9d72cede
EM
2956 $this->_outputMode, $attachments
2957 )
2958 ) {
6a488035
TO
2959 CRM_Core_Session::setStatus(ts("Report mail has been sent."), ts('Sent'), 'success');
2960 }
2961 else {
2962 CRM_Core_Session::setStatus(ts("Report mail could not be sent."), ts('Mail Error'), 'error');
2963 }
824aede6 2964 return TRUE;
6a488035
TO
2965 }
2966 elseif ($this->_outputMode == 'print') {
2967 echo $content;
2968 }
2969 else {
2970 if ($chartType = CRM_Utils_Array::value('charts', $this->_params)) {
2971 $config = CRM_Core_Config::singleton();
2972 //get chart image name
2973 $chartImg = $this->_chartId . '.png';
2974 //get image url path
971d41b1
CW
2975 $uploadUrl
2976 = str_replace('/persist/contribute/', '/persist/', $config->imageUploadURL) .
9d72cede 2977 'openFlashChart/';
6a488035
TO
2978 $uploadUrl .= $chartImg;
2979 //get image doc path to overwrite
971d41b1
CW
2980 $uploadImg
2981 = str_replace('/persist/contribute/', '/persist/', $config->imageUploadDir) .
9d72cede 2982 'openFlashChart/' . $chartImg;
6a488035
TO
2983 //Load the image
2984 $chart = imagecreatefrompng($uploadUrl);
2fe3c48d 2985 //convert it into formatted png
6a488035
TO
2986 header('Content-type: image/png');
2987 //overwrite with same image
2988 imagepng($chart, $uploadImg);
2989 //delete the object
2990 imagedestroy($chart);
2991 }
2992 CRM_Utils_PDF_Utils::html2pdf($content, "CiviReport.pdf", FALSE, array('orientation' => 'landscape'));
2993 }
2994 CRM_Utils_System::civiExit();
2995 }
2996 elseif ($this->_outputMode == 'csv') {
2997 CRM_Report_Utils_Report::export2csv($this, $rows);
2998 }
2999 elseif ($this->_outputMode == 'group') {
3000 $group = $this->_params['groups'];
3001 $this->add2group($group);
3002 }
3003 elseif ($this->_instanceButtonName == $this->controller->getButtonName()) {
3004 CRM_Report_Form_Instance::postProcess($this);
3005 }
3006 elseif ($this->_createNewButtonName == $this->controller->getButtonName() ||
9d72cede
EM
3007 $this->_outputMode == 'create_report'
3008 ) {
6a488035
TO
3009 $this->_createNew = TRUE;
3010 CRM_Report_Form_Instance::postProcess($this);
3011 }
3012 }
8f1445ea 3013
8a925f33
EM
3014 /**
3015 * Set store result set indicator to TRUE
3016 * @todo explain what this does
3017 */
00be9182 3018 public function storeResultSet() {
ae555e90
DS
3019 $this->_storeResultSet = TRUE;
3020 }
3021
74cf4551
EM
3022 /**
3023 * @return bool
3024 */
00be9182 3025 public function getResultSet() {
ae555e90
DS
3026 return $this->_resultSet;
3027 }
3028
74cf4551 3029 /**
fe482240 3030 * Use the form name to create the tpl file name.
74cf4551
EM
3031 *
3032 * @return string
74cf4551 3033 */
00be9182 3034 public function getTemplateFileName() {
6a488035 3035 $defaultTpl = parent::getTemplateFileName();
9d72cede 3036 $template = CRM_Core_Smarty::singleton();
6a488035
TO
3037 if (!$template->template_exists($defaultTpl)) {
3038 $defaultTpl = 'CRM/Report/Form.tpl';
3039 }
3040 return $defaultTpl;
3041 }
3042
688d37c6 3043 /**
fe482240 3044 * Compile the report content.
688d37c6 3045 * Although this function is super-short it is useful to keep separate so it can be over-ridden by report classes.
6a488035 3046 *
74cf4551
EM
3047 * @return string
3048 */
00be9182 3049 public function compileContent() {
8aac22c8 3050 $templateFile = $this->getHookedTemplateFileName();
9d72cede
EM
3051 return $this->_formValues['report_header'] .
3052 CRM_Core_Form::$_template->fetch($templateFile) .
3053 $this->_formValues['report_footer'];
6a488035
TO
3054 }
3055
3056
00be9182 3057 public function postProcess() {
6a488035
TO
3058 // get ready with post process params
3059 $this->beginPostProcess();
3060
3061 // build query
3062 $sql = $this->buildQuery();
3063
3064 // build array of result based on column headers. This method also allows
3065 // modifying column headers before using it to build result set i.e $rows.
3066 $rows = array();
3067 $this->buildRows($sql, $rows);
3068
3069 // format result set.
3070 $this->formatDisplay($rows);
3071
3072 // assign variables to templates
3073 $this->doTemplateAssignment($rows);
3074
3075 // do print / pdf / instance stuff if needed
3076 $this->endPostProcess($rows);
3077 }
3078
74cf4551
EM
3079 /**
3080 * @param int $rowCount
688d37c6 3081 * @return array
74cf4551 3082 */
00be9182 3083 public function limit($rowCount = self::ROW_COUNT_LIMIT) {
6a488035
TO
3084 // lets do the pager if in html mode
3085 $this->_limit = NULL;
77b97be7 3086
dbb4a0f9
PN
3087 // CRM-14115, over-ride row count if rowCount is specified in URL
3088 if ($this->_dashBoardRowCount) {
3089 $rowCount = $this->_dashBoardRowCount;
3090 }
6a488035
TO
3091 if ($this->_outputMode == 'html' || $this->_outputMode == 'group') {
3092 $this->_select = str_ireplace('SELECT ', 'SELECT SQL_CALC_FOUND_ROWS ', $this->_select);
3093
3094 $pageId = CRM_Utils_Request::retrieve('crmPID', 'Integer', CRM_Core_DAO::$_nullObject);
3095
8a925f33
EM
3096 // @todo all http vars should be extracted in the preProcess
3097 // - not randomly in the class
6a488035
TO
3098 if (!$pageId && !empty($_POST)) {
3099 if (isset($_POST['PagerBottomButton']) && isset($_POST['crmPID_B'])) {
8a925f33 3100 $pageId = max((int) $_POST['crmPID_B'], 1);
6a488035
TO
3101 }
3102 elseif (isset($_POST['PagerTopButton']) && isset($_POST['crmPID'])) {
8a925f33 3103 $pageId = max((int) $_POST['crmPID'], 1);
6a488035
TO
3104 }
3105 unset($_POST['crmPID_B'], $_POST['crmPID']);
3106 }
3107
3108 $pageId = $pageId ? $pageId : 1;
3109 $this->set(CRM_Utils_Pager::PAGE_ID, $pageId);
3110 $offset = ($pageId - 1) * $rowCount;
3111
bf00d1b6
DL
3112 $offset = CRM_Utils_Type::escape($offset, 'Int');
3113 $rowCount = CRM_Utils_Type::escape($rowCount, 'Int');
3114
dd3a4117 3115 $this->_limit = " LIMIT $offset, $rowCount";
6a488035
TO
3116 return array($offset, $rowCount);
3117 }
9d72cede
EM
3118 if ($this->_limitValue) {
3119 if ($this->_offsetValue) {
6f900755
E
3120 $this->_limit = " LIMIT {$this->_offsetValue}, {$this->_limitValue} ";
3121 }
3122 else {
3123 $this->_limit = " LIMIT " . $this->_limitValue;
3124 }
3125 }
6a488035
TO
3126 }
3127
74cf4551
EM
3128 /**
3129 * @param int $rowCount
3130 */
00be9182 3131 public function setPager($rowCount = self::ROW_COUNT_LIMIT) {
dbb4a0f9
PN
3132
3133 // CRM-14115, over-ride row count if rowCount is specified in URL
3134 if ($this->_dashBoardRowCount) {
3135 $rowCount = $this->_dashBoardRowCount;
3136 }
3137
6a488035 3138 if ($this->_limit && ($this->_limit != '')) {
9d72cede 3139 $sql = "SELECT FOUND_ROWS();";
6a488035 3140 $this->_rowsFound = CRM_Core_DAO::singleValueQuery($sql);
9d72cede 3141 $params = array(
6a488035
TO
3142 'total' => $this->_rowsFound,
3143 'rowCount' => $rowCount,
3144 'status' => ts('Records') . ' %%StatusMessage%%',
3145 'buttonBottom' => 'PagerBottomButton',
3146 'buttonTop' => 'PagerTopButton',
3147 'pageID' => $this->get(CRM_Utils_Pager::PAGE_ID),
3148 );
3149
3150 $pager = new CRM_Utils_Pager($params);
3151 $this->assign_by_ref('pager', $pager);
ecc20f0e 3152 $this->ajaxResponse['totalRows'] = $this->_rowsFound;
6a488035
TO
3153 }
3154 }
3155
74cf4551
EM
3156 /**
3157 * @param $field
3158 * @param $value
3159 * @param $op
3160 *
3161 * @return string
3162 */
00be9182 3163 public function whereGroupClause($field, $value, $op) {
6a488035
TO
3164
3165 $smartGroupQuery = "";
3166
3167 $group = new CRM_Contact_DAO_Group();
3168 $group->is_active = 1;
3169 $group->find();
3170 $smartGroups = array();
3171 while ($group->fetch()) {
9d72cede
EM
3172 if (in_array($group->id, $this->_params['gid_value']) &&
3173 $group->saved_search_id
3174 ) {
6a488035
TO
3175 $smartGroups[] = $group->id;
3176 }
3177 }
3178
3179 CRM_Contact_BAO_GroupContactCache::check($smartGroups);
3180
3181 $smartGroupQuery = '';
3182 if (!empty($smartGroups)) {
3183 $smartGroups = implode(',', $smartGroups);
3184 $smartGroupQuery = " UNION DISTINCT
3185 SELECT DISTINCT smartgroup_contact.contact_id
3186 FROM civicrm_group_contact_cache smartgroup_contact
3187 WHERE smartgroup_contact.group_id IN ({$smartGroups}) ";
3188 }
3189
29fc2b79 3190 $sqlOp = $this->getSQLOperator($op);
6a488035
TO
3191 if (!is_array($value)) {
3192 $value = array($value);
3193 }
3194 $clause = "{$field['dbAlias']} IN (" . implode(', ', $value) . ")";
3195
3196 return " {$this->_aliases['civicrm_contact']}.id {$sqlOp} (
3197 SELECT DISTINCT {$this->_aliases['civicrm_group']}.contact_id
3198 FROM civicrm_group_contact {$this->_aliases['civicrm_group']}
3199 WHERE {$clause} AND {$this->_aliases['civicrm_group']}.status = 'Added'
3200 {$smartGroupQuery} ) ";
3201 }
3202
74cf4551
EM
3203 /**
3204 * @param $field
3205 * @param $value
3206 * @param $op
3207 *
3208 * @return string
3209 */
00be9182 3210 public function whereTagClause($field, $value, $op) {
6a488035
TO
3211 // not using left join in query because if any contact
3212 // belongs to more than one tag, results duplicate
3213 // entries.
29fc2b79 3214 $sqlOp = $this->getSQLOperator($op);
6a488035
TO
3215 if (!is_array($value)) {
3216 $value = array($value);
3217 }
3218 $clause = "{$field['dbAlias']} IN (" . implode(', ', $value) . ")";
ed795723
JM
3219 $entity_table = $this->_tagFilterTable;
3220 return " {$this->_aliases[$entity_table]}.id {$sqlOp} (
6a488035
TO
3221 SELECT DISTINCT {$this->_aliases['civicrm_tag']}.entity_id
3222 FROM civicrm_entity_tag {$this->_aliases['civicrm_tag']}
ed795723 3223 WHERE entity_table = '$entity_table' AND {$clause} ) ";
6a488035
TO
3224 }
3225
f587aa17
EM
3226 /**
3227 * Generate membership organization clause.
3228 *
3229 * @param mixed $value
3230 * @param string $op SQL Operator
3231 *
3232 * @return string
3233 */
3234 public function whereMembershipOrgClause($value, $op) {
114a2c85
DG
3235 $sqlOp = $this->getSQLOperator($op);
3236 if (!is_array($value)) {
3237 $value = array($value);
3238 }
258e2add 3239
114a2c85
DG
3240 $tmp_membership_org_sql_list = implode(', ', $value);
3241 return " {$this->_aliases['civicrm_contact']}.id {$sqlOp} (
97709b72 3242 SELECT DISTINCT mem.contact_id
258e2add 3243 FROM civicrm_membership mem
97709b72 3244 LEFT JOIN civicrm_membership_status mem_status ON mem.status_id = mem_status.id
258e2add 3245 LEFT JOIN civicrm_membership_type mt ON mem.membership_type_id = mt.id
9d72cede
EM
3246 WHERE mt.member_of_contact_id IN (" .
3247 $tmp_membership_org_sql_list . ")
97709b72
DG
3248 AND mt.is_active = '1'
3249 AND mem_status.is_current_member = '1'
3250 AND mem_status.is_active = '1' ) ";
9d72cede 3251 }
114a2c85 3252
f587aa17 3253 /**
fe482240 3254 * Generate Membership Type SQL Clause.
f587aa17
EM
3255 * @param mixed $value
3256 * @param string $op
3257 *
3258 * @return string
3259 * SQL query string
3260 */
3261 public function whereMembershipTypeClause($value, $op) {
114a2c85
DG
3262 $sqlOp = $this->getSQLOperator($op);
3263 if (!is_array($value)) {
3264 $value = array($value);
3265 }
258e2add 3266
9d72cede 3267 $tmp_membership_sql_list = implode(', ', $value);
114a2c85 3268 return " {$this->_aliases['civicrm_contact']}.id {$sqlOp} (
97709b72 3269 SELECT DISTINCT mem.contact_id
258e2add 3270 FROM civicrm_membership mem
3271 LEFT JOIN civicrm_membership_status mem_status ON mem.status_id = mem_status.id
3272 LEFT JOIN civicrm_membership_type mt ON mem.membership_type_id = mt.id
9d72cede
EM
3273 WHERE mem.membership_type_id IN (" .
3274 $tmp_membership_sql_list . ")
97709b72
DG
3275 AND mt.is_active = '1'
3276 AND mem_status.is_current_member = '1'
3277 AND mem_status.is_active = '1' ) ";
114a2c85 3278 }
258e2add 3279
74cf4551
EM
3280 /**
3281 * @param string $tableAlias
3282 */
00be9182 3283 public function buildACLClause($tableAlias = 'contact_a') {
6a488035
TO
3284 list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias);
3285 }
3286
74cf4551
EM
3287 /**
3288 * @param bool $addFields
3289 * @param array $permCustomGroupIds
3290 */
00be9182 3291 public function addCustomDataToColumns($addFields = TRUE, $permCustomGroupIds = array()) {
6a488035
TO
3292 if (empty($this->_customGroupExtends)) {
3293 return;
3294 }
3295 if (!is_array($this->_customGroupExtends)) {
3296 $this->_customGroupExtends = array($this->_customGroupExtends);
3297 }
3298 $customGroupWhere = '';
3299 if (!empty($permCustomGroupIds)) {
9d72cede
EM
3300 $customGroupWhere = "cg.id IN (" . implode(',', $permCustomGroupIds) .
3301 ") AND";
6a488035
TO
3302 }
3303 $sql = "
3304SELECT cg.table_name, cg.title, cg.extends, cf.id as cf_id, cf.label,
3305 cf.column_name, cf.data_type, cf.html_type, cf.option_group_id, cf.time_format
3306FROM civicrm_custom_group cg
3307INNER JOIN civicrm_custom_field cf ON cg.id = cf.custom_group_id
3308WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
3309 {$customGroupWhere}
3310 cg.is_active = 1 AND
3311 cf.is_active = 1 AND
3312 cf.is_searchable = 1
3313ORDER BY cg.weight, cf.weight";
3314 $customDAO = CRM_Core_DAO::executeQuery($sql);
3315
3316 $curTable = NULL;
3317 while ($customDAO->fetch()) {
3318 if ($customDAO->table_name != $curTable) {
3319 $curTable = $customDAO->table_name;
3320 $curFields = $curFilters = array();
3321
3322 // dummy dao object
3323 $this->_columns[$curTable]['dao'] = 'CRM_Contact_DAO_Contact';
3324 $this->_columns[$curTable]['extends'] = $customDAO->extends;
3325 $this->_columns[$curTable]['grouping'] = $customDAO->table_name;
3326 $this->_columns[$curTable]['group_title'] = $customDAO->title;
3327
3328 foreach (array(
9d72cede
EM
3329 'fields',
3330 'filters',
21dfd5f5 3331 'group_bys',
9d72cede 3332 ) as $colKey) {
6a488035
TO
3333 if (!array_key_exists($colKey, $this->_columns[$curTable])) {
3334 $this->_columns[$curTable][$colKey] = array();
3335 }
3336 }
3337 }
3338 $fieldName = 'custom_' . $customDAO->cf_id;
3339
3340 if ($addFields) {
3341 // this makes aliasing work in favor
3342 $curFields[$fieldName] = array(
3343 'name' => $customDAO->column_name,
3344 'title' => $customDAO->label,
3345 'dataType' => $customDAO->data_type,
3346 'htmlType' => $customDAO->html_type,
3347 );
3348 }
3349 if ($this->_customGroupFilters) {
3350 // this makes aliasing work in favor
3351 $curFilters[$fieldName] = array(
3352 'name' => $customDAO->column_name,
3353 'title' => $customDAO->label,
3354 'dataType' => $customDAO->data_type,
3355 'htmlType' => $customDAO->html_type,
3356 );
3357 }
3358
3359 switch ($customDAO->data_type) {
3360 case 'Date':
3361 // filters
3362 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_DATE;
3363 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_DATE;
3364 // CRM-6946, show time part for datetime date fields
3365 if ($customDAO->time_format) {
3366 $curFields[$fieldName]['type'] = CRM_Utils_Type::T_TIMESTAMP;
3367 }
3368 break;
3369
3370 case 'Boolean':
3371 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_SELECT;
9d72cede
EM
3372 $curFilters[$fieldName]['options'] = array(
3373 '' => ts('- select -'),
3374 1 => ts('Yes'),
3375 0 => ts('No'),
6a488035
TO
3376 );
3377 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_INT;
3378 break;
3379
3380 case 'Int':
3381 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_INT;
3382 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_INT;
3383 break;
3384
3385 case 'Money':
3386 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_FLOAT;
3387 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_MONEY;
3388 break;
3389
3390 case 'Float':
3391 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_FLOAT;
3392 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_FLOAT;
3393 break;
3394
3395 case 'String':
3396 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3397
3398 if (!empty($customDAO->option_group_id)) {
3399 if (in_array($customDAO->html_type, array(
9d72cede
EM
3400 'Multi-Select',
3401 'AdvMulti-Select',
21dfd5f5 3402 'CheckBox',
9d72cede 3403 ))) {
6a488035
TO
3404 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT_SEPARATOR;
3405 }
3406 else {
3407 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT;
3408 }
3409 if ($this->_customGroupFilters) {
3410 $curFilters[$fieldName]['options'] = array();
9d72cede 3411 $ogDAO = CRM_Core_DAO::executeQuery("SELECT ov.value, ov.label FROM civicrm_option_value ov WHERE ov.option_group_id = %1 ORDER BY ov.weight", array(
86bd39be
TO
3412 1 => array(
3413 $customDAO->option_group_id,
21dfd5f5
TO
3414 'Integer',
3415 ),
86bd39be 3416 ));
6a488035
TO
3417 while ($ogDAO->fetch()) {
3418 $curFilters[$fieldName]['options'][$ogDAO->value] = $ogDAO->label;
3419 }
6e57f5ca 3420 CRM_Utils_Hook::customFieldOptions($customDAO->cf_id, $curFilters[$fieldName]['options'], FALSE);
6a488035
TO
3421 }
3422 }
3423 break;
3424
3425 case 'StateProvince':
3426 if (in_array($customDAO->html_type, array(
21dfd5f5 3427 'Multi-Select State/Province',
9d72cede 3428 ))) {
6a488035
TO
3429 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT_SEPARATOR;
3430 }
3431 else {
3432 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT;
3433 }
3434 $curFilters[$fieldName]['options'] = CRM_Core_PseudoConstant::stateProvince();
3435 break;
3436
3437 case 'Country':
3438 if (in_array($customDAO->html_type, array(
21dfd5f5 3439 'Multi-Select Country',
9d72cede 3440 ))) {
6a488035
TO
3441 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT_SEPARATOR;
3442 }
3443 else {
3444 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT;
3445 }
3446 $curFilters[$fieldName]['options'] = CRM_Core_PseudoConstant::country();
3447 break;
3448
3449 case 'ContactReference':
3450 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3451 $curFilters[$fieldName]['name'] = 'display_name';
3452 $curFilters[$fieldName]['alias'] = "contact_{$fieldName}_civireport";
3453
3454 $curFields[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3455 $curFields[$fieldName]['name'] = 'display_name';
3456 $curFields[$fieldName]['alias'] = "contact_{$fieldName}_civireport";
3457 break;
3458
3459 default:
3460 $curFields[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3461 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3462 }
3463
3464 if (!array_key_exists('type', $curFields[$fieldName])) {
fc161185 3465 $curFields[$fieldName]['type'] = CRM_Utils_Array::value('type', $curFilters[$fieldName], array());
6a488035
TO
3466 }
3467
3468 if ($addFields) {
3469 $this->_columns[$curTable]['fields'] = array_merge($this->_columns[$curTable]['fields'], $curFields);
3470 }
3471 if ($this->_customGroupFilters) {
3472 $this->_columns[$curTable]['filters'] = array_merge($this->_columns[$curTable]['filters'], $curFilters);
3473 }
3474 if ($this->_customGroupGroupBy) {
3475 $this->_columns[$curTable]['group_bys'] = array_merge($this->_columns[$curTable]['group_bys'], $curFields);
3476 }
3477 }
3478 }
3479
00be9182 3480 public function customDataFrom() {
6a488035
TO
3481 if (empty($this->_customGroupExtends)) {
3482 return;
3483 }
3484 $mapper = CRM_Core_BAO_CustomQuery::$extendsMap;
3485
3486 foreach ($this->_columns as $table => $prop) {
9d72cede
EM
3487 if (substr($table, 0, 13) == 'civicrm_value' ||
3488 substr($table, 0, 12) == 'custom_value'
3489 ) {
6a488035
TO
3490 $extendsTable = $mapper[$prop['extends']];
3491
3492 // check field is in params
3493 if (!$this->isFieldSelected($prop)) {
3494 continue;
3495 }
4c9d78ea 3496 $baseJoin = CRM_Utils_Array::value($prop['extends'], $this->_customGroupExtendsJoin, "{$this->_aliases[$extendsTable]}.id");
6a488035 3497
9d72cede 3498 $customJoin = is_array($this->_customGroupJoin) ? $this->_customGroupJoin[$table] : $this->_customGroupJoin;
6a488035 3499 $this->_from .= "
aa1aa08e 3500{$customJoin} {$table} {$this->_aliases[$table]} ON {$this->_aliases[$table]}.entity_id = {$baseJoin}";
6a488035
TO
3501 // handle for ContactReference
3502 if (array_key_exists('fields', $prop)) {
3503 foreach ($prop['fields'] as $fieldName => $field) {
9d72cede
EM
3504 if (CRM_Utils_Array::value('dataType', $field) ==
3505 'ContactReference'
3506 ) {
6a488035
TO
3507 $columnName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', CRM_Core_BAO_CustomField::getKeyID($fieldName), 'column_name');
3508 $this->_from .= "
3509LEFT JOIN civicrm_contact {$field['alias']} ON {$field['alias']}.id = {$this->_aliases[$table]}.{$columnName} ";
3510 }
3511 }
3512 }
3513 }
3514 }
3515 }
3516
74cf4551
EM
3517 /**
3518 * @param $prop
3519 *
3520 * @return bool
3521 */
00be9182 3522 public function isFieldSelected($prop) {
6a488035
TO
3523 if (empty($prop)) {
3524 return FALSE;
3525 }
3526
3527 if (!empty($this->_params['fields'])) {
3528 foreach (array_keys($prop['fields']) as $fieldAlias) {
3529 $customFieldId = CRM_Core_BAO_CustomField::getKeyID($fieldAlias);
3530 if ($customFieldId) {
3531 if (array_key_exists($fieldAlias, $this->_params['fields'])) {
3532 return TRUE;
3533 }
3534
3535 //might be survey response field.
9d72cede
EM
3536 if (!empty($this->_params['fields']['survey_response']) &&
3537 !empty($prop['fields'][$fieldAlias]['isSurveyResponseField'])
3538 ) {
6a488035
TO
3539 return TRUE;
3540 }
3541 }
3542 }
3543 }
3544
3545 if (!empty($this->_params['group_bys']) && $this->_customGroupGroupBy) {
3546 foreach (array_keys($prop['group_bys']) as $fieldAlias) {
9d72cede
EM
3547 if (array_key_exists($fieldAlias, $this->_params['group_bys']) &&
3548 CRM_Core_BAO_CustomField::getKeyID($fieldAlias)
3549 ) {
6a488035
TO
3550 return TRUE;
3551 }
3552 }
3553 }
3554
3555 if (!empty($this->_params['order_bys'])) {
3556 foreach (array_keys($prop['fields']) as $fieldAlias) {
3557 foreach ($this->_params['order_bys'] as $orderBy) {
9d72cede
EM
3558 if ($fieldAlias == $orderBy['column'] &&
3559 CRM_Core_BAO_CustomField::getKeyID($fieldAlias)
3560 ) {
6a488035
TO
3561 return TRUE;
3562 }
3563 }
3564 }
3565 }
3566
3567 if (!empty($prop['filters']) && $this->_customGroupFilters) {
3568 foreach ($prop['filters'] as $fieldAlias => $val) {
3569 foreach (array(
9d72cede
EM
3570 'value',
3571 'min',
3572 'max',
3573 'relative',
3574 'from',
21dfd5f5 3575 'to',
9d72cede 3576 ) as $attach) {
6a488035 3577 if (isset($this->_params[$fieldAlias . '_' . $attach]) &&
dfe4b2f5 3578 (!empty($this->_params[$fieldAlias . '_' . $attach])
9d72cede
EM
3579 || ($attach != 'relative' &&
3580 $this->_params[$fieldAlias . '_' . $attach] == '0')
dfe4b2f5 3581 )
9d72cede 3582 ) {
6a488035
TO
3583 return TRUE;
3584 }
3585 }
a7488080 3586 if (!empty($this->_params[$fieldAlias . '_op']) &&
6a488035
TO
3587 in_array($this->_params[$fieldAlias . '_op'], array('nll', 'nnll'))
3588 ) {
3589 return TRUE;
3590 }
3591 }
3592 }
3593
3594 return FALSE;
3595 }
3596
3597 /**
3598 * Check for empty order_by configurations and remove them; also set
3599 * template to hide them.
537c70b8
EM
3600 *
3601 * @param array $formValues
6a488035 3602 */
00be9182 3603 public function preProcessOrderBy(&$formValues) {
6a488035 3604 // Object to show/hide form elements
ae555e90 3605 $_showHide = new CRM_Core_ShowHideBlocks('', '');
6a488035
TO
3606
3607 $_showHide->addShow('optionField_1');
3608
3609 // Cycle through order_by options; skip any empty ones, and hide them as well
3610 $n = 1;
3611
3612 if (!empty($formValues['order_bys'])) {
3613 foreach ($formValues['order_bys'] as $order_by) {
3614 if ($order_by['column'] && $order_by['column'] != '-') {
3615 $_showHide->addShow('optionField_' . $n);
3616 $orderBys[$n] = $order_by;
3617 $n++;
3618 }
3619 }
3620 }
3621 for ($i = $n; $i <= 5; $i++) {
3622 if ($i > 1) {
3623 $_showHide->addHide('optionField_' . $i);
3624 }
3625 }
3626
3627 // overwrite order_by options with modified values
3628 if (!empty($orderBys)) {
3629 $formValues['order_bys'] = $orderBys;
3630 }
3631 else {
3632 $formValues['order_bys'] = array(1 => array('column' => '-'));
3633 }
3634
3635 // assign show/hide data to template
3636 $_showHide->addToTemplate();
3637 }
3638
3639 /**
3640 * Does table name have columns in SELECT clause?
3641 *
7e06c9f5
TO
3642 * @param string $tableName
3643 * Name of table (index of $this->_columns array).
6a488035
TO
3644 *
3645 * @return bool
3646 */
00be9182 3647 public function isTableSelected($tableName) {
6a488035
TO
3648 return in_array($tableName, $this->selectedTables());
3649 }
3650
3651 /**
fe482240 3652 * Fetch array of DAO tables having columns included in SELECT or ORDER BY clause.
6a488035
TO
3653 * (building the array if it's unset)
3654 *
971d41b1
CW
3655 * @return array
3656 * selectedTables
6a488035 3657 */
00be9182 3658 public function selectedTables() {
6a488035
TO
3659 if (!$this->_selectedTables) {
3660 $orderByColumns = array();
9d72cede
EM
3661 if (array_key_exists('order_bys', $this->_params) &&
3662 is_array($this->_params['order_bys'])
3663 ) {
6a488035
TO
3664 foreach ($this->_params['order_bys'] as $orderBy) {
3665 $orderByColumns[] = $orderBy['column'];
3666 }
3667 }
3668
3669 foreach ($this->_columns as $tableName => $table) {
3670 if (array_key_exists('fields', $table)) {
3671 foreach ($table['fields'] as $fieldName => $field) {
9d72cede
EM
3672 if (!empty($field['required']) ||
3673 !empty($this->_params['fields'][$fieldName])
3674 ) {
6a488035
TO
3675 $this->_selectedTables[] = $tableName;
3676 break;
3677 }
3678 }
3679 }
3680 if (array_key_exists('order_bys', $table)) {
3681 foreach ($table['order_bys'] as $orderByName => $orderBy) {
3682 if (in_array($orderByName, $orderByColumns)) {
3683 $this->_selectedTables[] = $tableName;
3684 break;
3685 }
3686 }
3687 }
3688 if (array_key_exists('filters', $table)) {
3689 foreach ($table['filters'] as $filterName => $filter) {
a7488080 3690 if (!empty($this->_params["{$filterName}_value"]) ||
9d72cede
EM
3691 CRM_Utils_Array::value("{$filterName}_op", $this->_params) ==
3692 'nll' ||
3693 CRM_Utils_Array::value("{$filterName}_op", $this->_params) ==
3694 'nnll'
6a488035
TO
3695 ) {
3696 $this->_selectedTables[] = $tableName;
3697 break;
3698 }
3699 }
3700 }
3701 }
3702 }
3703 return $this->_selectedTables;
3704 }
3705
850e4640
E
3706 /**
3707 * @deprecated - use getAddressColumns which is a more accurate description
3708 * and also accepts an array of options rather than a long list
3709 *
c490a46a 3710 * adding address fields to construct function in reports
77b97be7 3711 *
7e06c9f5
TO
3712 * @param bool $groupBy
3713 * Add GroupBy? Not appropriate for detail report.
3714 * @param bool $orderBy
3715 * Add GroupBy? Not appropriate for detail report.
77b97be7
EM
3716 * @param bool $filters
3717 * @param array $defaults
3718 *
a6c01b45
CW
3719 * @return array
3720 * address fields for construct clause
6a488035 3721 */
00be9182 3722 public function addAddressFields($groupBy = TRUE, $orderBy = FALSE, $filters = TRUE, $defaults = array('country_id' => TRUE)) {
6a488035 3723 $addressFields = array(
9d72cede 3724 'civicrm_address' => array(
6a488035 3725 'dao' => 'CRM_Core_DAO_Address',
9d72cede
EM
3726 'fields' => array(
3727 'name' => array(
3728 'title' => ts('Address Name'),
6a488035
TO
3729 'default' => CRM_Utils_Array::value('name', $defaults, FALSE),
3730 ),
9d72cede
EM
3731 'street_address' => array(
3732 'title' => ts('Street Address'),
6a488035
TO
3733 'default' => CRM_Utils_Array::value('street_address', $defaults, FALSE),
3734 ),
9d72cede
EM
3735 'supplemental_address_1' => array(
3736 'title' => ts('Supplementary Address Field 1'),
6a488035
TO
3737 'default' => CRM_Utils_Array::value('supplemental_address_1', $defaults, FALSE),
3738 ),
9d72cede
EM
3739 'supplemental_address_2' => array(
3740 'title' => ts('Supplementary Address Field 2'),
6a488035
TO
3741 'default' => CRM_Utils_Array::value('supplemental_address_2', $defaults, FALSE),
3742 ),
9d72cede 3743 'street_number' => array(
6a488035
TO
3744 'name' => 'street_number',
3745 'title' => ts('Street Number'),
3746 'type' => 1,
3747 'default' => CRM_Utils_Array::value('street_number', $defaults, FALSE),
3748 ),
9d72cede 3749 'street_name' => array(
6a488035
TO
3750 'name' => 'street_name',
3751 'title' => ts('Street Name'),
3752 'type' => 1,
3753 'default' => CRM_Utils_Array::value('street_name', $defaults, FALSE),
3754 ),
9d72cede 3755 'street_unit' => array(
6a488035
TO
3756 'name' => 'street_unit',
3757 'title' => ts('Street Unit'),
3758 'type' => 1,
3759 'default' => CRM_Utils_Array::value('street_unit', $defaults, FALSE),
3760 ),
9d72cede
EM
3761 'city' => array(
3762 'title' => ts('City'),
6a488035
TO
3763 'default' => CRM_Utils_Array::value('city', $defaults, FALSE),
3764 ),
9d72cede
EM
3765 'postal_code' => array(
3766 'title' => ts('Postal Code'),
6a488035 3767 'default' => CRM_Utils_Array::value('postal_code', $defaults, FALSE),
4cbdd2b1 3768 ),
9d72cede
EM
3769 'postal_code_suffix' => array(
3770 'title' => ts('Postal Code Suffix'),
4cbdd2b1 3771 'default' => CRM_Utils_Array::value('postal_code_suffix', $defaults, FALSE),
6a488035 3772 ),
9d72cede
EM
3773 'country_id' => array(
3774 'title' => ts('Country'),
3775 'default' => CRM_Utils_Array::value('country_id', $defaults, FALSE),
3776 ),
3777 'state_province_id' => array(
3778 'title' => ts('State/Province'),
6a488035
TO
3779 'default' => CRM_Utils_Array::value('state_province_id', $defaults, FALSE),
3780 ),
9d72cede
EM
3781 'county_id' => array(
3782 'title' => ts('County'),
3783 'default' => CRM_Utils_Array::value('county_id', $defaults, FALSE),
3784 ),
6a488035
TO
3785 ),
3786 'grouping' => 'location-fields',
3787 ),
3788 );
3789
3790 if ($filters) {
3791 $addressFields['civicrm_address']['filters'] = array(
9d72cede
EM
3792 'street_number' => array(
3793 'title' => ts('Street Number'),
3794 'type' => 1,
3795 'name' => 'street_number',
6a488035 3796 ),
9d72cede
EM
3797 'street_name' => array(
3798 'title' => ts('Street Name'),
3799 'name' => 'street_name',
3800 'operator' => 'like',
6a488035 3801 ),
9d72cede
EM
3802 'postal_code' => array(
3803 'title' => ts('Postal Code'),
3804 'type' => 1,
3805 'name' => 'postal_code',
6a488035 3806 ),
9d72cede
EM
3807 'city' => array(
3808 'title' => ts('City'),
3809 'operator' => 'like',
3810 'name' => 'city',
6a488035 3811 ),
863581d1
CW
3812 'country_id' => array(
3813 'name' => 'country_id',
3814 'title' => ts('Country'),
6a488035 3815 'type' => CRM_Utils_Type::T_INT,
9d72cede 3816 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
c927c151 3817 'options' => CRM_Core_PseudoConstant::country(),
6a488035
TO
3818 ),
3819 'state_province_id' => array(
3820 'name' => 'state_province_id',
3821 'title' => ts('State/Province'),
3822 'type' => CRM_Utils_Type::T_INT,
c927c151
CW
3823 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
3824 'options' => array(),
6a488035 3825 ),
863581d1
CW
3826 'county_id' => array(
3827 'name' => 'county_id',
3828 'title' => ts('County'),
6a488035 3829 'type' => CRM_Utils_Type::T_INT,
c927c151
CW
3830 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
3831 'options' => array(),
6a488035
TO
3832 ),
3833 );
3834 }
3835
3836 if ($orderBy) {
9d72cede
EM
3837 $addressFields['civicrm_address']['order_bys'] = array(
3838 'street_name' => array('title' => ts('Street Name')),
3839 'street_number' => array('title' => 'Odd / Even Street Number'),
3840 'street_address' => NULL,
3841 'city' => NULL,
3842 'postal_code' => NULL,
6a488035
TO
3843 );
3844 }
3845
3846 if ($groupBy) {
3847 $addressFields['civicrm_address']['group_bys'] = array(
3848 'street_address' => NULL,
3849 'city' => NULL,
3850 'postal_code' => NULL,
9d72cede
EM
3851 'state_province_id' => array(
3852 'title' => ts('State/Province'),
6a488035 3853 ),
9d72cede
EM
3854 'country_id' => array(
3855 'title' => ts('Country'),
6a488035 3856 ),
9d72cede
EM
3857 'county_id' => array(
3858 'title' => ts('County'),
6a488035
TO
3859 ),
3860 );
3861 }
3862 return $addressFields;
3863 }
3864
74cf4551 3865 /**
fe482240 3866 * Do AlterDisplay processing on Address Fields.
688d37c6 3867 *
74cf4551
EM
3868 * @param $row
3869 * @param $rows
3870 * @param $rowNum
3871 * @param $baseUrl
3872 * @param $urltxt
3873 *
3874 * @return bool
3875 */
00be9182 3876 public function alterDisplayAddressFields(&$row, &$rows, &$rowNum, $baseUrl, $urltxt) {
6a488035
TO
3877 $criteriaQueryParams = CRM_Report_Utils_Report::getPreviewCriteriaQueryParams($this->_defaults, $this->_params);
3878 $entryFound = FALSE;
3879 // handle country
3880 if (array_key_exists('civicrm_address_country_id', $row)) {
3881 if ($value = $row['civicrm_address_country_id']) {
3882 $rows[$rowNum]['civicrm_address_country_id'] = CRM_Core_PseudoConstant::country($value, FALSE);
3883 $url = CRM_Report_Utils_Report::getNextUrl($baseUrl,
9d72cede
EM
3884 "reset=1&force=1&{$criteriaQueryParams}&" .
3885 "country_id_op=in&country_id_value={$value}",
3886 $this->_absoluteUrl, $this->_id
6a488035
TO
3887 );
3888 $rows[$rowNum]['civicrm_address_country_id_link'] = $url;
3889 $rows[$rowNum]['civicrm_address_country_id_hover'] = ts("%1 for this country.",
9d72cede 3890 array(1 => $urltxt)
6a488035
TO
3891 );
3892 }
3893
3894 $entryFound = TRUE;
3895 }
3896 if (array_key_exists('civicrm_address_county_id', $row)) {
3897 if ($value = $row['civicrm_address_county_id']) {
3898 $rows[$rowNum]['civicrm_address_county_id'] = CRM_Core_PseudoConstant::county($value, FALSE);
3899 $url = CRM_Report_Utils_Report::getNextUrl($baseUrl,
9d72cede
EM
3900 "reset=1&force=1&{$criteriaQueryParams}&" .
3901 "county_id_op=in&county_id_value={$value}",
3902 $this->_absoluteUrl, $this->_id
6a488035
TO
3903 );
3904 $rows[$rowNum]['civicrm_address_county_id_link'] = $url;
3905 $rows[$rowNum]['civicrm_address_county_id_hover'] = ts("%1 for this county.",
9d72cede 3906 array(1 => $urltxt)
6a488035
TO
3907 );
3908 }
3909 $entryFound = TRUE;
3910 }
3911 // handle state province
3912 if (array_key_exists('civicrm_address_state_province_id', $row)) {
3913 if ($value = $row['civicrm_address_state_province_id']) {
3914 $rows[$rowNum]['civicrm_address_state_province_id'] = CRM_Core_PseudoConstant::stateProvince($value, FALSE);
3915
3916 $url = CRM_Report_Utils_Report::getNextUrl($baseUrl,
9d72cede
EM
3917 "reset=1&force=1&{$criteriaQueryParams}&state_province_id_op=in&state_province_id_value={$value}",
3918 $this->_absoluteUrl, $this->_id
6a488035
TO
3919 );
3920 $rows[$rowNum]['civicrm_address_state_province_id_link'] = $url;
3921 $rows[$rowNum]['civicrm_address_state_province_id_hover'] = ts("%1 for this state.",
9d72cede 3922 array(1 => $urltxt)
6a488035
TO
3923 );
3924 }
3925 $entryFound = TRUE;
3926 }
3927
3928 return $entryFound;
3929 }
3930
74cf4551 3931 /**
688d37c6
CW
3932 * Adjusts dates passed in to YEAR() for fiscal year.
3933 *
100fef9d 3934 * @param string $fieldName
74cf4551
EM
3935 *
3936 * @return string
3937 */
00be9182 3938 public function fiscalYearOffset($fieldName) {
6a488035
TO
3939 $config = CRM_Core_Config::singleton();
3940 $fy = $config->fiscalYearStart;
9d72cede
EM
3941 if (CRM_Utils_Array::value('yid_op', $this->_params) == 'calendar' ||
3942 ($fy['d'] == 1 && $fy['M'] == 1)
3943 ) {
6a488035
TO
3944 return "YEAR( $fieldName )";
3945 }
9d72cede
EM
3946 return "YEAR( $fieldName - INTERVAL " . ($fy['M'] - 1) . " MONTH" .
3947 ($fy['d'] > 1 ? (" - INTERVAL " . ($fy['d'] - 1) . " DAY") : '') . " )";
6a488035
TO
3948 }
3949
688d37c6 3950 /**
fe482240 3951 * Add Address into From Table if required.
6a488035 3952 */
00be9182 3953 public function addAddressFromClause() {
6a488035
TO
3954 // include address field if address column is to be included
3955 if ((isset($this->_addressField) &&
3956 $this->_addressField
3957 ) ||
3958 $this->isTableSelected('civicrm_address')
3959 ) {
3960 $this->_from .= "
3961 LEFT JOIN civicrm_address {$this->_aliases['civicrm_address']}
3962 ON ({$this->_aliases['civicrm_contact']}.id =
3963 {$this->_aliases['civicrm_address']}.contact_id) AND
3964 {$this->_aliases['civicrm_address']}.is_primary = 1\n";
3965 }
3966 }
3967
850e4640 3968 /**
fe482240 3969 * Add Phone into From Table if required.
850e4640 3970 */
00be9182 3971 public function addPhoneFromClause() {
850e4640
E
3972 // include address field if address column is to be included
3973 if ($this->isTableSelected('civicrm_phone')
3974 ) {
3975 $this->_from .= "
3976 LEFT JOIN civicrm_phone {$this->_aliases['civicrm_phone']}
3977 ON ({$this->_aliases['civicrm_contact']}.id =
3978 {$this->_aliases['civicrm_phone']}.contact_id) AND
3979 {$this->_aliases['civicrm_phone']}.is_primary = 1\n";
3980 }
3981 }
3982
3983 /**
fe482240 3984 * Get phone columns to add to array.
9d72cede 3985 *
850e4640 3986 * @param array $options
16b10e64
CW
3987 * - prefix Prefix to add to table (in case of more than one instance of the table)
3988 * - prefix_label Label to give columns from this phone table instance
9d72cede 3989 *
a6c01b45
CW
3990 * @return array
3991 * phone columns definition
850e4640 3992 */
00be9182 3993 public function getPhoneColumns($options = array()) {
850e4640
E
3994 $defaultOptions = array(
3995 'prefix' => '',
3996 'prefix_label' => '',
3997 );
3998
9d72cede 3999 $options = array_merge($defaultOptions, $options);
850e4640
E
4000
4001 $fields = array(
4002 $options['prefix'] . 'civicrm_phone' => array(
9d72cede 4003 'dao' => 'CRM_Core_DAO_Phone',
850e4640
E
4004 'fields' => array(
4005 $options['prefix'] . 'phone' => array(
4006 'title' => ts($options['prefix_label'] . 'Phone'),
21dfd5f5 4007 'name' => 'phone',
850e4640
E
4008 ),
4009 ),
4010 ),
4011 );
4012 return $fields;
4013 }
4014
4015 /**
fe482240 4016 * Get address columns to add to array.
9d72cede 4017 *
850e4640 4018 * @param array $options
16b10e64
CW
4019 * - prefix Prefix to add to table (in case of more than one instance of the table)
4020 * - prefix_label Label to give columns from this address table instance
9d72cede 4021 *
a6c01b45
CW
4022 * @return array
4023 * address columns definition
850e4640 4024 */
00be9182 4025 public function getAddressColumns($options = array()) {
c927c151 4026 $options += array(
850e4640
E
4027 'prefix' => '',
4028 'prefix_label' => '',
4029 'group_by' => TRUE,
4030 'order_by' => TRUE,
4031 'filters' => TRUE,
c927c151 4032 'defaults' => array(),
850e4640 4033 );
850e4640
E
4034 return $this->addAddressFields(
4035 $options['group_by'],
4036 $options['order_by'],
4037 $options['filters'],
4038 $options['defaults']
4039 );
850e4640
E
4040 }
4041
74cf4551 4042 /**
100fef9d 4043 * @param int $groupID
74cf4551 4044 */
00be9182 4045 public function add2group($groupID) {
6a488035
TO
4046 if (is_numeric($groupID) && isset($this->_aliases['civicrm_contact'])) {
4047 $select = "SELECT DISTINCT {$this->_aliases['civicrm_contact']}.id AS addtogroup_contact_id, ";
4048 $select = str_ireplace('SELECT SQL_CALC_FOUND_ROWS ', $select, $this->_select);
4049
4050 $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy}";
4051 $dao = CRM_Core_DAO::executeQuery($sql);
4052
4053 $contact_ids = array();
4054 // Add resulting contacts to group
4055 while ($dao->fetch()) {
4056 if ($dao->addtogroup_contact_id) {
4057 $contact_ids[$dao->addtogroup_contact_id] = $dao->addtogroup_contact_id;
4058 }
4059 }
4060
9d72cede 4061 if (!empty($contact_ids)) {
6a488035
TO
4062 CRM_Contact_BAO_GroupContact::addContactsToGroup($contact_ids, $groupID);
4063 CRM_Core_Session::setStatus(ts("Listed contact(s) have been added to the selected group."), ts('Contacts Added'), 'success');
4064 }
4065 else {
4066 CRM_Core_Session::setStatus(ts("The listed records(s) cannot be added to the group."));
4067 }
4068 }
4069 }
46065582 4070
688d37c6 4071 /**
fe482240 4072 * function used for showing charts on print screen.
688d37c6 4073 */
00be9182 4074 public static function uploadChartImage() {
46065582 4075 // upload strictly for '.png' images
d04d4eef
PJ
4076 $name = trim(basename(CRM_Utils_Request::retrieve('name', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET')));
4077 if (preg_match('/\.png$/', $name)) {
46065582
PJ
4078 //
4079 // POST data is usually string data, but we are passing a RAW .png
4080 // so PHP is a bit confused and $_POST is empty. But it has saved
4081 // the raw bits into $HTTP_RAW_POST_DATA
4082 //
4083 $httpRawPostData = $GLOBALS['HTTP_RAW_POST_DATA'];
4084
4085 // prepare the directory
4086 $config = CRM_Core_Config::singleton();
971d41b1
CW
4087 $defaultPath
4088 = str_replace('/persist/contribute/', '/persist/', $config->imageUploadDir) .
9d72cede 4089 '/openFlashChart/';
46065582
PJ
4090 if (!file_exists($defaultPath)) {
4091 mkdir($defaultPath, 0777, TRUE);
4092 }
4093
4094 // full path to the saved image including filename
d04d4eef 4095 $destination = $defaultPath . $name;
46065582
PJ
4096
4097 //write and save
4098 $jfh = fopen($destination, 'w') or die("can't open file");
4099 fwrite($jfh, $httpRawPostData);
4100 fclose($jfh);
4101 CRM_Utils_System::civiExit();
4102 }
4103 }
2107cde9
CW
4104
4105 /**
fe482240 4106 * Apply common settings to entityRef fields.
9d72cede 4107 *
2107cde9
CW
4108 * @param array $field
4109 * @param string $table
4110 */
4111 private function setEntityRefDefaults(&$field, $table) {
4112 $field['attributes'] = $field['attributes'] ? $field['attributes'] : array();
4113 $field['attributes'] += array(
4114 'entity' => CRM_Core_DAO_AllCoreTables::getBriefName(CRM_Core_DAO_AllCoreTables::getClassForTable($table)),
4115 'multiple' => TRUE,
4116 'placeholder' => ts('- select -'),
4117 );
4118 }
96025800 4119
232624b1 4120}