Merge pull request #11988 from yashodha/translate_strings
[civicrm-core.git] / CRM / Report / Form.php
CommitLineData
6a488035 1<?php
6a488035 2/*
36241b02 3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
db4cd986 5 +--------------------------------------------------------------------+
8c9251b3 6 | Copyright CiviCRM LLC (c) 2004-2018 |
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/**
7d7c50f9 29 * Class CRM_Report_Form
6a488035
TO
30 */
31class CRM_Report_Form extends CRM_Core_Form {
7da04cde 32 const ROW_COUNT_LIMIT = 50;
6a488035
TO
33
34 /**
35 * Operator types - used for displaying filter elements
36 */
7da04cde 37 const
9d72cede 38 OP_INT = 1,
6a488035 39 OP_STRING = 2,
9d72cede 40 OP_DATE = 4,
6a488035 41 OP_DATETIME = 5,
9d72cede 42 OP_FLOAT = 8,
6a488035
TO
43 OP_SELECT = 64,
44 OP_MULTISELECT = 65,
45 OP_MULTISELECT_SEPARATOR = 66,
2107cde9
CW
46 OP_MONTH = 128,
47 OP_ENTITYREF = 256;
6a488035
TO
48
49 /**
50 * The id of the report instance
51 *
52 * @var integer
53 */
54 protected $_id;
55
56 /**
57 * The id of the report template
58 *
59 * @var integer;
60 */
61 protected $_templateID;
62
63 /**
64 * The report title
65 *
66 * @var string
67 */
68 protected $_title;
69 protected $_noFields = FALSE;
70
71 /**
72 * The set of all columns in the report. An associative array
537c70b8 73 * with column name as the key and attributes as the value
6a488035
TO
74 *
75 * @var array
76 */
77 protected $_columns = array();
78
79 /**
80 * The set of filters in the report
81 *
82 * @var array
83 */
84 protected $_filters = array();
85
86 /**
87 * The set of optional columns in the report
88 *
89 * @var array
90 */
2eee184e 91 public $_options = array();
6a488035 92
688d37c6 93 /**
6a488035
TO
94 * By default most reports hide contact id.
95 * Setting this to true makes it available
96 */
97 protected $_exposeContactID = TRUE;
98
99 /**
100 * Set of statistic fields
101 *
102 * @var array
103 */
104 protected $_statFields = array();
105
106 /**
107 * Set of statistics data
108 *
109 * @var array
110 */
111 protected $_statistics = array();
112
113 /**
114 * List of fields not to be repeated during display
115 *
116 * @var array
117 */
118 protected $_noRepeats = array();
119
120 /**
121 * List of fields not to be displayed
122 *
123 * @var array
124 */
125 protected $_noDisplay = array();
126
127 /**
128 * Object type that a custom group extends
129 *
130 * @var null
131 */
132 protected $_customGroupExtends = NULL;
aa1aa08e 133 protected $_customGroupExtendsJoin = array();
6a488035
TO
134 protected $_customGroupFilters = TRUE;
135 protected $_customGroupGroupBy = FALSE;
9d72cede 136 protected $_customGroupJoin = 'LEFT JOIN';
6a488035
TO
137
138 /**
100fef9d 139 * Build tags filter
6a488035
TO
140 */
141 protected $_tagFilter = FALSE;
142
143 /**
ed795723 144 * specify entity table for tags filter
ed795723
JM
145 */
146 protected $_tagFilterTable = 'civicrm_contact';
147
6a488035 148 /**
0b62c1ab 149 * Build groups filter.
6a488035 150 *
f587aa17 151 * @var bool
6a488035
TO
152 */
153 protected $_groupFilter = FALSE;
154
43c1fa19 155 /**
156 * Has the report been optimised for group filtering.
157 *
158 * The functionality for group filtering has been improved but not
159 * all reports have been adjusted to take care of it.
160 *
161 * This property exists to highlight the reports which are still using the
162 * slow method & allow group filtering to still work for them until they
163 * can be migrated.
164 *
165 * In order to protect extensions we have to default to TRUE - but I have
166 * separately marked every class with a groupFilter in the hope that will trigger
167 * people to fix them as they touch them.
168 *
169 * CRM-19170
170 *
171 * @var bool
172 */
173 protected $groupFilterNotOptimised = TRUE;
174
6a488035
TO
175 /**
176 * Navigation fields
177 *
178 * @var array
179 */
180 public $_navigation = array();
181
182 public $_drilldownReport = array();
183
50951061 184 /**
0b62c1ab
EM
185 * Array of tabs to display on report.
186 *
187 * E.g we define the tab title, the tpl and the tab-specific part of the css or html link.
188 *
189 * $this->tabs['OrderBy'] = array(
190 * 'title' => ts('Sorting'),
191 * 'tpl' => 'OrderBy',
192 * 'div_label' => 'order-by',
193 * );
50951061
EM
194 *
195 * @var array
196 */
197 protected $tabs = array();
198
182f5081 199 /**
200 * Should we add paging.
201 *
202 * @var bool
203 */
204 protected $addPaging = TRUE;
205
81a22d3d 206 protected $isForceGroupBy = FALSE;
207
208 protected $groupConcatTested = FALSE;
209
6a488035
TO
210 /**
211 * An attribute for checkbox/radio form field layout
212 *
213 * @var array
214 */
215 protected $_fourColumnAttribute = array(
9d72cede
EM
216 '</td><td width="25%">',
217 '</td><td width="25%">',
218 '</td><td width="25%">',
219 '</tr><tr><td>',
6a488035
TO
220 );
221
222 protected $_force = 1;
223
224 protected $_params = NULL;
225 protected $_formValues = NULL;
226 protected $_instanceValues = NULL;
227
228 protected $_instanceForm = FALSE;
229 protected $_criteriaForm = FALSE;
230
231 protected $_instanceButtonName = NULL;
232 protected $_createNewButtonName = NULL;
233 protected $_printButtonName = NULL;
234 protected $_pdfButtonName = NULL;
235 protected $_csvButtonName = NULL;
236 protected $_groupButtonName = NULL;
237 protected $_chartButtonName = NULL;
238 protected $_csvSupported = TRUE;
239 protected $_add2groupSupported = TRUE;
240 protected $_groups = NULL;
9b0380d9 241 protected $_grandFlag = FALSE;
6a488035
TO
242 protected $_rowsFound = NULL;
243 protected $_selectAliases = array();
244 protected $_rollup = NULL;
6f900755 245
43c1fa19 246 /**
247 * Table containing list of contact IDs within the group filter.
248 *
249 * @var string
250 */
251 protected $groupTempTable = '';
252
f587aa17
EM
253 /**
254 * @var array
255 */
256 protected $_aliases = array();
257
258 /**
259 * @var string
260 */
261 protected $_where;
262
263 /**
264 * @var string
265 */
266 protected $_from;
267
6f900755
E
268 /**
269 * SQL Limit clause
270 * @var string
271 */
6a488035 272 protected $_limit = NULL;
6f900755
E
273
274 /**
275 * This can be set to specify a limit to the number of rows
276 * Since it is currently envisaged as part of the api usage it is only being applied
277 * when $_output mode is not 'html' or 'group' so as not to have to interpret / mess with that part
182f5081 278 * of the code (see limit() fn.
279 *
6f900755
E
280 * @var integer
281 */
282 protected $_limitValue = NULL;
283
284 /**
285 * This can be set to specify row offset
286 * See notes on _limitValue
287 * @var integer
288 */
289 protected $_offsetValue = NULL;
cdb44cba
EM
290 /**
291 * @var null
292 */
44d59d04 293 protected $_sections = NULL;
6a488035
TO
294 protected $_autoIncludeIndexedFieldsAsOrderBys = 0;
295 protected $_absoluteUrl = FALSE;
296
ae555e90
DS
297 /**
298 * Flag to indicate if result-set is to be stored in a class variable which could be retrieved using getResultSet() method.
299 *
300 * @var boolean
301 */
302 protected $_storeResultSet = FALSE;
303
304 /**
305 * When _storeResultSet Flag is set use this var to store result set in form of array
306 *
307 * @var boolean
308 */
309 protected $_resultSet = array();
310
6a488035
TO
311 /**
312 * To what frequency group-by a date column
313 *
314 * @var array
315 */
316 protected $_groupByDateFreq = array(
317 'MONTH' => 'Month',
318 'YEARWEEK' => 'Week',
319 'QUARTER' => 'Quarter',
320 'YEAR' => 'Year',
321 );
322
323 /**
324 * Variables to hold the acl inner join and where clause
325 */
326 protected $_aclFrom = NULL;
327 protected $_aclWhere = NULL;
328
329 /**
568a0946 330 * Array of DAO tables having columns included in SELECT or ORDER BY clause.
331 *
332 * Where has also been added to this although perhaps the 'includes both' array should have a different name.
6a488035
TO
333 *
334 * @var array
335 */
f0384ec0 336 protected $_selectedTables = array();
6a488035 337
568a0946 338 /**
339 * Array of DAO tables having columns included in WHERE or HAVING clause
340 *
341 * @var array
342 */
343 protected $filteredTables;
344
c58f66e0 345 /**
dc0c71cf 346 * Output mode e.g 'print', 'csv', 'pdf'.
347 *
c58f66e0
E
348 * @var string
349 */
f63fae91 350 protected $_outputMode;
c58f66e0 351
dc0c71cf 352 /**
353 * Format of any chart in use.
354 *
355 * (it's unclear if this could be merged with outputMode at this stage)
356 *
357 * @var
358 */
359 protected $_format;
360
6a488035
TO
361 public $_having = NULL;
362 public $_select = NULL;
1f220d30 363 public $_selectClauses = array();
6a488035
TO
364 public $_columnHeaders = array();
365 public $_orderBy = NULL;
f2947aea 366 public $_orderByFields = array();
9d72cede 367 public $_orderByArray = array();
70e504f2 368 /**
369 * Array of clauses to group by.
370 *
371 * @var array
372 */
373 protected $_groupByArray = array();
6a488035 374 public $_groupBy = NULL;
adfe2750 375 public $_whereClauses = array();
376 public $_havingClauses = array();
1c4d8c3e 377
dbb4a0f9 378 /**
100fef9d 379 * DashBoardRowCount Dashboard row count
dbb4a0f9
PN
380 * @var Integer
381 */
382 public $_dashBoardRowCount;
383
c58f66e0
E
384 /**
385 * Is this being called without a form controller (ie. the report is being render outside the normal form
386 * - e.g the api is retrieving the rows
387 * @var boolean
388 */
389 public $noController = FALSE;
390
7a961f19 391 /**
392 * Variable to hold the currency alias
393 */
394 protected $_currencyColumn = NULL;
6a488035 395
2fe3c48d
EM
396 /**
397 * @var string
398 */
399 protected $_interval;
400
401 /**
402 * @var bool
403 */
404 protected $_sendmail;
405
406 /**
407 * @var int
408 */
409 protected $_chartId;
410
9907633b
EM
411 /**
412 * @var int
413 */
44d59d04 414 public $_section;
9907633b
EM
415
416 /**
417 * @var string Report description.
418 */
15ba35a3 419 public $_description;
9907633b
EM
420
421 /**
422 * @var bool Is an address field selected.
423 * This was intended to determine if the address table should be joined in
424 * The isTableSelected function is now preferred for this purpose
425 */
426 protected $_addressField;
427
428 /**
429 * @var bool Is an email field selected.
430 * This was intended to determine if the email table should be joined in
431 * The isTableSelected function is now preferred for this purpose
432 */
433 protected $_emailField;
434
435 /**
436 * @var bool Is a phone field selected.
437 * This was intended to determine if the phone table should be joined in
438 * The isTableSelected function is now preferred for this purpose
439 */
440 protected $_phoneField;
441
442 /**
443 * @var bool Create new report instance? (or update existing) on save.
444 */
445 protected $_createNew;
446
8a2bee47 447 /**
448 * When a grand total row has calculated the status we pop it off to here.
449 *
450 * This allows us to access it from the stats function and avoid recalculating.
451 */
452 protected $rollupRow = array();
453
9099266e
WM
454 /**
455 * @var string Database attributes - character set and collation
456 */
d09afb3e 457 protected $_databaseAttributes = ' DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci';
9099266e 458
02d451ab
EM
459 /**
460 * SQL being run in this report.
461 *
462 * The sql in the report is stored in this variable in order to be displayed on the developer tab.
463 *
464 * @var string
465 */
466
467 protected $sql;
55f71fa7 468
81a22d3d 469 /**
470 * An instruction not to add a Group By.
471 *
472 * This is relevant where the group by might be otherwise added after the code that determines the group by array.
473 *
474 * e.g. where stat fields are being added but other settings cause it to not be desirable to add a group by
475 * such as in pivot charts when no row header is set
476 *
477 * @var bool
478 */
479 protected $noGroupBy = FALSE;
480
55f71fa7 481 /**
482 * SQL being run in this report as an array.
483 *
484 * The sql in the report is stored in this variable in order to be returned to api & test calls.
485 *
486 * @var string
487 */
488
489 protected $sqlArray;
6a488035 490 /**
7d7c50f9 491 * Class constructor.
6a488035 492 */
00be9182 493 public function __construct() {
6a488035
TO
494 parent::__construct();
495
22b67281
CW
496 $this->addClass('crm-report-form');
497
6a488035
TO
498 if ($this->_tagFilter) {
499 $this->buildTagFilter();
500 }
501 if ($this->_exposeContactID) {
502 if (array_key_exists('civicrm_contact', $this->_columns)) {
503 $this->_columns['civicrm_contact']['fields']['exposed_id'] = array(
504 'name' => 'id',
ccc29f8e 505 'title' => ts('Contact ID'),
6a488035
TO
506 'no_repeat' => TRUE,
507 );
508 }
509 }
510
511 if ($this->_groupFilter) {
512 $this->buildGroupFilter();
513 }
514
515 // Get all custom groups
cd43c5e3 516 $allGroups = CRM_Core_PseudoConstant::get('CRM_Core_DAO_CustomField', 'custom_group_id');
6a488035 517
4f8ec8be
DS
518 // Get the custom groupIds for which the user has VIEW permission
519 // If the user has 'access all custom data' permission, we'll leave $permCustomGroupIds empty
520 // and addCustomDataToColumns() will allow access to all custom groups.
521 $permCustomGroupIds = array();
522 if (!CRM_Core_Permission::check('access all custom data')) {
523 $permCustomGroupIds = CRM_ACL_API::group(CRM_Core_Permission::VIEW, NULL, 'civicrm_custom_group', $allGroups, NULL);
524 // do not allow custom data for reports if user doesn't have
525 // permission to access custom data.
526 if (!empty($this->_customGroupExtends) && empty($permCustomGroupIds)) {
527 $this->_customGroupExtends = array();
528 }
6a488035
TO
529 }
530
531 // merge custom data columns to _columns list, if any
532 $this->addCustomDataToColumns(TRUE, $permCustomGroupIds);
533
534 // add / modify display columns, filters ..etc
535 CRM_Utils_Hook::alterReportVar('columns', $this->_columns, $this);
7a961f19 536
537 //assign currencyColumn variable to tpl
538 $this->assign('currencyColumn', $this->_currencyColumn);
6a488035
TO
539 }
540
0b62c1ab
EM
541 /**
542 * Shared pre-process function.
543 *
544 * If overriding preProcess function this should still be called.
545 *
546 * @throws \Exception
547 */
00be9182 548 public function preProcessCommon() {
1273d77c 549 $this->_force = CRM_Utils_Request::retrieve('force', 'Boolean');
77b97be7 550
1273d77c 551 $this->_dashBoardRowCount = CRM_Utils_Request::retrieve('rowCount', 'Integer');
6a488035 552
a3d827a7 553 $this->_section = CRM_Utils_Request::retrieve('section', 'Integer');
6a488035
TO
554
555 $this->assign('section', $this->_section);
556 CRM_Core_Region::instance('page-header')->add(array(
557 'markup' => sprintf('<!-- Report class: [%s] -->', htmlentities(get_class($this))),
558 ));
9d72cede 559 if (!$this->noController) {
c58f66e0 560 $this->setID($this->get('instanceId'));
6a488035 561
6a488035 562 if (!$this->_id) {
c58f66e0
E
563 $this->setID(CRM_Report_Utils_Report::getInstanceID());
564 if (!$this->_id) {
9d72cede 565 $this->setID(CRM_Report_Utils_Report::getInstanceIDForPath());
c58f66e0 566 }
6a488035 567 }
6a488035 568
c58f66e0
E
569 // set qfkey so that pager picks it up and use it in the "Next > Last >>" links.
570 // FIXME: Note setting it in $_GET doesn't work, since pager generates link based on QUERY_STRING
571 $_SERVER['QUERY_STRING'] .= "&qfKey={$this->controller->_key}";
572 }
6a488035
TO
573
574 if ($this->_id) {
575 $this->assign('instanceId', $this->_id);
576 $params = array('id' => $this->_id);
577 $this->_instanceValues = array();
0b25329b 578 CRM_Core_DAO::commonRetrieve('CRM_Report_DAO_ReportInstance',
6a488035
TO
579 $params,
580 $this->_instanceValues
581 );
582 if (empty($this->_instanceValues)) {
583 CRM_Core_Error::fatal("Report could not be loaded.");
584 }
7cab1323 585 $this->_title = $this->_instanceValues['title'];
6a488035
TO
586 if (!empty($this->_instanceValues['permission']) &&
587 (!(CRM_Core_Permission::check($this->_instanceValues['permission']) ||
588 CRM_Core_Permission::check('administer Reports')
589 ))
590 ) {
591 CRM_Utils_System::permissionDenied();
592 CRM_Utils_System::civiExit();
593 }
594
595 $formValues = CRM_Utils_Array::value('form_values', $this->_instanceValues);
596 if ($formValues) {
597 $this->_formValues = unserialize($formValues);
598 }
599 else {
600 $this->_formValues = NULL;
601 }
602
182f5081 603 $this->setOutputMode();
604
605 if ($this->_outputMode == 'copy') {
14cdc4cb 606 $this->_createNew = TRUE;
182f5081 607 $this->_params = $this->_formValues;
608 $this->_params['view_mode'] = 'criteria';
8f864776 609 $this->_params['title'] = $this->getTitle() . ts(' (copy created by %1 on %2)', array(
610 CRM_Core_Session::singleton()->getLoggedInContactDisplayName(),
611 CRM_Utils_Date::customFormat(date('Y-m-d H:i')),
612 ));
182f5081 613 // Do not pass go. Do not collect another chance to re-run the same query.
614 CRM_Report_Form_Instance::postProcess($this);
615 }
616
6a488035 617 // lets always do a force if reset is found in the url.
e6e7e540 618 // Hey why not? see CRM-17225 for more about this. The use of reset to be force is historical for reasons stated
619 // in the comment line above these 2.
182f5081 620 if (!empty($_REQUEST['reset'])
621 && !in_array(CRM_Utils_Request::retrieve('output', 'String'), array('save', 'criteria'))) {
6a488035
TO
622 $this->_force = 1;
623 }
624
625 // set the mode
626 $this->assign('mode', 'instance');
627 }
c58f66e0 628 elseif (!$this->noController) {
6a488035
TO
629 list($optionValueID, $optionValue) = CRM_Report_Utils_Report::getValueIDFromUrl();
630 $instanceCount = CRM_Report_Utils_Report::getInstanceCount($optionValue);
631 if (($instanceCount > 0) && $optionValueID) {
632 $this->assign('instanceUrl',
633 CRM_Utils_System::url('civicrm/report/list',
634 "reset=1&ovid=$optionValueID"
635 )
636 );
637 }
638 if ($optionValueID) {
639 $this->_description = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $optionValueID, 'description');
640 }
641
642 // set the mode
643 $this->assign('mode', 'template');
644 }
645
646 // lets display the Report Settings section
647 $this->_instanceForm = $this->_force || $this->_id || (!empty($_POST));
648
649 // Do not display Report Settings section if administer Reports permission is absent OR
650 // if report instance is reserved and administer reserved reports absent
651 if (!CRM_Core_Permission::check('administer Reports') ||
9d72cede
EM
652 ($this->_instanceValues['is_reserved'] &&
653 !CRM_Core_Permission::check('administer reserved reports'))
654 ) {
6a488035
TO
655 $this->_instanceForm = FALSE;
656 }
657
658 $this->assign('criteriaForm', FALSE);
659 // Display Report Criteria section if user has access Report Criteria OR administer Reports AND report instance is not reserved
9d72cede
EM
660 if (CRM_Core_Permission::check('administer Reports') ||
661 CRM_Core_Permission::check('access Report Criteria')
662 ) {
663 if (!$this->_instanceValues['is_reserved'] ||
664 CRM_Core_Permission::check('administer reserved reports')
665 ) {
6a488035
TO
666 $this->assign('criteriaForm', TRUE);
667 $this->_criteriaForm = TRUE;
668 }
669 }
670
92991caf 671 // Special permissions check for private instance if it's not the current contact instance
766eb4ba 672 if ($this->_id &&
2d9a4a4c 673 (CRM_Report_BAO_ReportInstance::reportIsPrivate($this->_id) &&
674 !CRM_Report_BAO_ReportInstance::contactIsOwner($this->_id))) {
92991caf
SV
675 if (!CRM_Core_Permission::check('access all private reports')) {
676 $this->_instanceForm = FALSE;
677 $this->assign('criteriaForm', FALSE);
678 }
679 }
680
6a488035
TO
681 $this->_instanceButtonName = $this->getButtonName('submit', 'save');
682 $this->_createNewButtonName = $this->getButtonName('submit', 'next');
6a488035
TO
683 $this->_groupButtonName = $this->getButtonName('submit', 'group');
684 $this->_chartButtonName = $this->getButtonName('submit', 'chart');
685 }
686
0b62c1ab
EM
687 /**
688 * Add bread crumb.
689 */
00be9182 690 public function addBreadCrumb() {
971d41b1
CW
691 $breadCrumbs
692 = array(
8f1445ea
DL
693 array(
694 'title' => ts('Report Templates'),
695 'url' => CRM_Utils_System::url('civicrm/admin/report/template/list', 'reset=1'),
21dfd5f5 696 ),
8f1445ea 697 );
6a488035
TO
698
699 CRM_Utils_System::appendBreadCrumb($breadCrumbs);
700 }
701
0b62c1ab
EM
702 /**
703 * Pre process function.
704 *
705 * Called prior to build form.
706 */
00be9182 707 public function preProcess() {
8f1445ea 708 $this->preProcessCommon();
6a488035 709
6a488035 710 if (!$this->_id) {
29fc2b79 711 $this->addBreadCrumb();
6a488035
TO
712 }
713
714 foreach ($this->_columns as $tableName => $table) {
8bb36676 715 $this->setTableAlias($table, $tableName);
6a488035 716
f63c531a 717 $expFields = array();
6a488035 718 // higher preference to bao object
f63c531a 719 $daoOrBaoName = CRM_Utils_Array::value('bao', $table, CRM_Utils_Array::value('dao', $table));
720
721 if ($daoOrBaoName) {
722 if (method_exists($daoOrBaoName, 'exportableFields')) {
723 $expFields = $daoOrBaoName::exportableFields();
724 }
725 else {
726 $expFields = $daoOrBaoName::export();
727 }
8edb9849 728 }
6a488035 729
507d1635 730 $doNotCopy = array('required', 'default');
6a488035
TO
731
732 $fieldGroups = array('fields', 'filters', 'group_bys', 'order_bys');
733 foreach ($fieldGroups as $fieldGrp) {
a7488080 734 if (!empty($table[$fieldGrp]) && is_array($table[$fieldGrp])) {
6a488035
TO
735 foreach ($table[$fieldGrp] as $fieldName => $field) {
736 // $name is the field name used to reference the BAO/DAO export fields array
737 $name = isset($field['name']) ? $field['name'] : $fieldName;
738
739 // Sometimes the field name key in the BAO/DAO export fields array is
740 // different from the actual database field name.
741 // Unset $field['name'] so that actual database field name can be obtained
742 // from the BAO/DAO export fields array.
743 unset($field['name']);
744
745 if (array_key_exists($name, $expFields)) {
746 foreach ($doNotCopy as $dnc) {
747 // unset the values we don't want to be copied.
748 unset($expFields[$name][$dnc]);
749 }
750 if (empty($field)) {
751 $this->_columns[$tableName][$fieldGrp][$fieldName] = $expFields[$name];
752 }
753 else {
754 foreach ($expFields[$name] as $property => $val) {
755 if (!array_key_exists($property, $field)) {
756 $this->_columns[$tableName][$fieldGrp][$fieldName][$property] = $val;
757 }
758 }
759 }
760 }
761
762 // fill other vars
a7488080 763 if (!empty($field['no_repeat'])) {
6a488035
TO
764 $this->_noRepeats[] = "{$tableName}_{$fieldName}";
765 }
a7488080 766 if (!empty($field['no_display'])) {
6a488035
TO
767 $this->_noDisplay[] = "{$tableName}_{$fieldName}";
768 }
769
770 // set alias = table-name, unless already set
971d41b1
CW
771 $alias = isset($field['alias']) ? $field['alias'] : (
772 isset($this->_columns[$tableName]['alias']) ? $this->_columns[$tableName]['alias'] : $tableName
6a488035
TO
773 );
774 $this->_columns[$tableName][$fieldGrp][$fieldName]['alias'] = $alias;
775
776 // set name = fieldName, unless already set
777 if (!isset($this->_columns[$tableName][$fieldGrp][$fieldName]['name'])) {
778 $this->_columns[$tableName][$fieldGrp][$fieldName]['name'] = $name;
779 }
780
81a22d3d 781 if (!isset($this->_columns[$tableName][$fieldGrp][$fieldName]['table_name'])) {
782 $this->_columns[$tableName][$fieldGrp][$fieldName]['table_name'] = $tableName;
783 }
784
6a488035
TO
785 // set dbAlias = alias.name, unless already set
786 if (!isset($this->_columns[$tableName][$fieldGrp][$fieldName]['dbAlias'])) {
971d41b1
CW
787 $this->_columns[$tableName][$fieldGrp][$fieldName]['dbAlias']
788 = $alias . '.' .
9d72cede 789 $this->_columns[$tableName][$fieldGrp][$fieldName]['name'];
6a488035
TO
790 }
791
c75e90fa 792 // a few auto fills for filters
c93f6d83 793 if ($fieldGrp == 'filters') {
c75e90fa
DS
794 // fill operator types
795 if (!array_key_exists('operatorType', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
796 switch (CRM_Utils_Array::value('type', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
797 case CRM_Utils_Type::T_MONEY:
798 case CRM_Utils_Type::T_FLOAT:
799 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_FLOAT;
800 break;
ea100cb5 801
c75e90fa
DS
802 case CRM_Utils_Type::T_INT:
803 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_INT;
804 break;
ea100cb5 805
c75e90fa 806 case CRM_Utils_Type::T_DATE:
c93f6d83 807 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_DATE;
c75e90fa 808 break;
ea100cb5 809
c75e90fa
DS
810 case CRM_Utils_Type::T_BOOLEAN:
811 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_SELECT;
812 if (!array_key_exists('options', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
971d41b1
CW
813 $this->_columns[$tableName][$fieldGrp][$fieldName]['options']
814 = array(
9d72cede
EM
815 '' => ts('Any'),
816 '0' => ts('No'),
21dfd5f5 817 '1' => ts('Yes'),
9d72cede 818 );
c75e90fa
DS
819 }
820 break;
ea100cb5 821
c75e90fa 822 default:
c93f6d83 823 if ($daoOrBaoName &&
9d72cede
EM
824 array_key_exists('pseudoconstant', $this->_columns[$tableName][$fieldGrp][$fieldName])
825 ) {
c75e90fa
DS
826 // with multiple options operator-type is generally multi-select
827 $this->_columns[$tableName][$fieldGrp][$fieldName]['operatorType'] = CRM_Report_Form::OP_MULTISELECT;
828 if (!array_key_exists('options', $this->_columns[$tableName][$fieldGrp][$fieldName])) {
829 // fill options
830 $this->_columns[$tableName][$fieldGrp][$fieldName]['options'] = CRM_Core_PseudoConstant::get($daoOrBaoName, $fieldName);
831 }
832 }
833 break;
834 }
6a488035
TO
835 }
836 }
534c4d20 837 if (!isset($this->_columns[$tableName]['metadata'][$fieldName])) {
838 $this->_columns[$tableName]['metadata'][$fieldName] = $this->_columns[$tableName][$fieldGrp][$fieldName];
839 }
6a488035
TO
840 }
841 }
842 }
843
844 // copy filters to a separate handy variable
845 if (array_key_exists('filters', $table)) {
846 $this->_filters[$tableName] = $this->_columns[$tableName]['filters'];
847 }
848
849 if (array_key_exists('group_bys', $table)) {
850 $groupBys[$tableName] = $this->_columns[$tableName]['group_bys'];
851 }
852
853 if (array_key_exists('fields', $table)) {
854 $reportFields[$tableName] = $this->_columns[$tableName]['fields'];
855 }
856 }
857
858 if ($this->_force) {
859 $this->setDefaultValues(FALSE);
860 }
861
8f1445ea
DL
862 CRM_Report_Utils_Get::processFilter($this->_filters, $this->_defaults);
863 CRM_Report_Utils_Get::processGroupBy($groupBys, $this->_defaults);
864 CRM_Report_Utils_Get::processFields($reportFields, $this->_defaults);
6a488035
TO
865 CRM_Report_Utils_Get::processChart($this->_defaults);
866
867 if ($this->_force) {
868 $this->_formValues = $this->_defaults;
869 $this->postProcess();
870 }
871 }
872
74cf4551 873 /**
7d7c50f9
EM
874 * Set default values.
875 *
74cf4551
EM
876 * @param bool $freeze
877 *
878 * @return array
879 */
00be9182 880 public function setDefaultValues($freeze = TRUE) {
6a488035
TO
881 $freezeGroup = array();
882
883 // FIXME: generalizing form field naming conventions would reduce
7d7c50f9 884 // Lots of lines below.
6a488035
TO
885 foreach ($this->_columns as $tableName => $table) {
886 if (array_key_exists('fields', $table)) {
887 foreach ($table['fields'] as $fieldName => $field) {
a7488080 888 if (empty($field['no_display'])) {
094ab048 889 if (!empty($field['required'])) {
6a488035
TO
890 // set default
891 $this->_defaults['fields'][$fieldName] = 1;
892
893 if ($freeze) {
894 // find element object, so that we could use quickform's freeze method
895 // for required elements
8f1445ea 896 $obj = $this->getElementFromGroup("fields", $fieldName);
6a488035
TO
897 if ($obj) {
898 $freezeGroup[] = $obj;
899 }
900 }
901 }
902 elseif (isset($field['default'])) {
903 $this->_defaults['fields'][$fieldName] = $field['default'];
904 }
905 }
906 }
907 }
908
909 if (array_key_exists('group_bys', $table)) {
910 foreach ($table['group_bys'] as $fieldName => $field) {
911 if (isset($field['default'])) {
a7488080 912 if (!empty($field['frequency'])) {
6a488035
TO
913 $this->_defaults['group_bys_freq'][$fieldName] = 'MONTH';
914 }
915 $this->_defaults['group_bys'][$fieldName] = $field['default'];
916 }
917 }
918 }
919 if (array_key_exists('filters', $table)) {
920 foreach ($table['filters'] as $fieldName => $field) {
921 if (isset($field['default'])) {
9d72cede
EM
922 if (CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_DATE
923 ) {
924 if (is_array($field['default'])) {
b0e34e7c 925 $this->_defaults["{$fieldName}_from"] = CRM_Utils_Array::value('from', $field['default']);
926 $this->_defaults["{$fieldName}_to"] = CRM_Utils_Array::value('to', $field['default']);
927 $this->_defaults["{$fieldName}_relative"] = 0;
928 }
9d72cede 929 else {
b0e34e7c 930 $this->_defaults["{$fieldName}_relative"] = $field['default'];
931 }
6a488035
TO
932 }
933 else {
2edb4e81 934 if ((CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_INT) && is_array($field['default'])) {
935 $this->_defaults["{$fieldName}_min"] = CRM_Utils_Array::value('min', $field['default']);
936 $this->_defaults["{$fieldName}_max"] = CRM_Utils_Array::value('max', $field['default']);
937 }
6a488035
TO
938 $this->_defaults["{$fieldName}_value"] = $field['default'];
939 }
940 }
941 //assign default value as "in" for multiselect
942 //operator, To freeze the select element
9d72cede
EM
943 if (CRM_Utils_Array::value('operatorType', $field) ==
944 CRM_Report_Form::OP_MULTISELECT
945 ) {
6a488035
TO
946 $this->_defaults["{$fieldName}_op"] = 'in';
947 }
9d72cede
EM
948 if (CRM_Utils_Array::value('operatorType', $field) ==
949 CRM_Report_Form::OP_ENTITYREF
950 ) {
2107cde9
CW
951 $this->_defaults["{$fieldName}_op"] = 'in';
952 }
9d72cede
EM
953 elseif (CRM_Utils_Array::value('operatorType', $field) ==
954 CRM_Report_Form::OP_MULTISELECT_SEPARATOR
955 ) {
6a488035
TO
956 $this->_defaults["{$fieldName}_op"] = 'mhas';
957 }
958 elseif ($op = CRM_Utils_Array::value('default_op', $field)) {
959 $this->_defaults["{$fieldName}_op"] = $op;
960 }
961 }
962 }
963
8f1445ea 964 if (
e76ce93e 965 empty($this->_formValues['order_bys']) &&
966 (array_key_exists('order_bys', $table) &&
967 is_array($table['order_bys']))
6a488035 968 ) {
6a488035 969 if (!array_key_exists('order_bys', $this->_defaults)) {
6a488035
TO
970 $this->_defaults['order_bys'] = array();
971 }
972 foreach ($table['order_bys'] as $fieldName => $field) {
8cc574cf 973 if (!empty($field['default']) || !empty($field['default_order']) ||
9d72cede
EM
974 CRM_Utils_Array::value('default_is_section', $field) ||
975 !empty($field['default_weight'])
976 ) {
6a488035
TO
977 $order_by = array(
978 'column' => $fieldName,
979 'order' => CRM_Utils_Array::value('default_order', $field, 'ASC'),
980 'section' => CRM_Utils_Array::value('default_is_section', $field, 0),
981 );
982
a7488080 983 if (!empty($field['default_weight'])) {
6a488035
TO
984 $this->_defaults['order_bys'][(int) $field['default_weight']] = $order_by;
985 }
986 else {
987 array_unshift($this->_defaults['order_bys'], $order_by);
988 }
989 }
990 }
991 }
992
993 foreach ($this->_options as $fieldName => $field) {
994 if (isset($field['default'])) {
995 $this->_defaults['options'][$fieldName] = $field['default'];
996 }
997 }
998 }
999
1000 if (!empty($this->_submitValues)) {
1001 $this->preProcessOrderBy($this->_submitValues);
1002 }
1003 else {
1004 $this->preProcessOrderBy($this->_defaults);
1005 }
1006
1007 // lets finish freezing task here itself
1008 if (!empty($freezeGroup)) {
1009 foreach ($freezeGroup as $elem) {
1010 $elem->freeze();
1011 }
1012 }
1013
1014 if ($this->_formValues) {
1015 $this->_defaults = array_merge($this->_defaults, $this->_formValues);
1016 }
1017
1018 if ($this->_instanceValues) {
1019 $this->_defaults = array_merge($this->_defaults, $this->_instanceValues);
1020 }
1021
1022 CRM_Report_Form_Instance::setDefaultValues($this, $this->_defaults);
1023
1024 return $this->_defaults;
1025 }
1026
74cf4551 1027 /**
7d7c50f9
EM
1028 * Get element from group.
1029 *
8a925f33 1030 * @param string $group
100fef9d 1031 * @param string $grpFieldName
74cf4551
EM
1032 *
1033 * @return bool
1034 */
00be9182 1035 public function getElementFromGroup($group, $grpFieldName) {
6a488035
TO
1036 $eleObj = $this->getElement($group);
1037 foreach ($eleObj->_elements as $index => $obj) {
1038 if ($grpFieldName == $obj->_attributes['name']) {
1039 return $obj;
1040 }
1041 }
1042 return FALSE;
1043 }
1044
c58f66e0 1045 /**
7d7c50f9 1046 * Setter for $_params.
9d72cede 1047 *
c58f66e0
E
1048 * @param array $params
1049 */
00be9182 1050 public function setParams($params) {
c58f66e0
E
1051 $this->_params = $params;
1052 }
1053
1054 /**
0b62c1ab 1055 * Setter for $_id.
2a6da8d7 1056 *
537c70b8 1057 * @param int $instanceID
c58f66e0 1058 */
537c70b8
EM
1059 public function setID($instanceID) {
1060 $this->_id = $instanceID;
c58f66e0
E
1061 }
1062
1063 /**
07f44165 1064 * Setter for $_force.
9d72cede 1065 *
317a8023 1066 * @param bool $isForce
9d72cede 1067 */
00be9182 1068 public function setForce($isForce) {
c58f66e0
E
1069 $this->_force = $isForce;
1070 }
6f900755
E
1071
1072 /**
7d7c50f9 1073 * Setter for $_limitValue.
9d72cede 1074 *
d3e86119 1075 * @param int $_limitValue
6f900755 1076 */
00be9182 1077 public function setLimitValue($_limitValue) {
6f900755
E
1078 $this->_limitValue = $_limitValue;
1079 }
1080
1081 /**
7d7c50f9 1082 * Setter for $_offsetValue.
9d72cede 1083 *
d3e86119 1084 * @param int $_offsetValue
6f900755 1085 */
00be9182 1086 public function setOffsetValue($_offsetValue) {
6f900755
E
1087 $this->_offsetValue = $_offsetValue;
1088 }
1089
182f5081 1090 /**
1091 * Setter for $addPaging.
1092 *
1093 * @param bool $value
1094 */
1095 public function setAddPaging($value) {
1096 $this->addPaging = $value;
1097 }
1098
c58f66e0 1099 /**
7d7c50f9
EM
1100 * Getter for $_defaultValues.
1101 *
a6c01b45 1102 * @return array
c58f66e0 1103 */
00be9182 1104 public function getDefaultValues() {
c58f66e0
E
1105 return $this->_defaults;
1106 }
1107
7d7c50f9
EM
1108 /**
1109 * Add columns to report.
1110 */
00be9182 1111 public function addColumns() {
6a488035
TO
1112 $options = array();
1113 $colGroups = NULL;
1114 foreach ($this->_columns as $tableName => $table) {
1115 if (array_key_exists('fields', $table)) {
1116 foreach ($table['fields'] as $fieldName => $field) {
8bdc861c 1117 $groupTitle = '';
a7488080 1118 if (empty($field['no_display'])) {
9d72cede 1119 foreach (array('table', 'field') as $var) {
8bdc861c
DS
1120 if (!empty(${$var}['grouping'])) {
1121 if (!is_array(${$var}['grouping'])) {
1122 $tableName = ${$var}['grouping'];
9d72cede
EM
1123 }
1124 else {
b80138e0
DS
1125 $tableName = array_keys(${$var}['grouping']);
1126 $tableName = $tableName[0];
1127 $groupTitle = array_values(${$var}['grouping']);
1128 $groupTitle = $groupTitle[0];
8bdc861c
DS
1129 }
1130 }
6a488035 1131 }
8bdc861c
DS
1132
1133 if (!$groupTitle && isset($table['group_title'])) {
1134 $groupTitle = $table['group_title'];
6df2d404 1135 // Having a group_title is secret code for being a custom group
b44e3f84 1136 // which cryptically translates to needing an accordion.
6df2d404
EM
1137 // here we make that explicit.
1138 $colGroups[$tableName]['use_accordian_for_field_selection'] = TRUE;
6a488035 1139 }
6a488035 1140
8bdc861c 1141 $colGroups[$tableName]['fields'][$fieldName] = CRM_Utils_Array::value('title', $field);
8cc574cf 1142 if ($groupTitle && empty($colGroups[$tableName]['group_title'])) {
8bdc861c 1143 $colGroups[$tableName]['group_title'] = $groupTitle;
6a488035 1144 }
6a488035
TO
1145 $options[$fieldName] = CRM_Utils_Array::value('title', $field);
1146 }
1147 }
1148 }
1149 }
1150
1151 $this->addCheckBox("fields", ts('Select Columns'), $options, NULL,
1152 NULL, NULL, NULL, $this->_fourColumnAttribute, TRUE
1153 );
0b62c1ab
EM
1154 if (!empty($colGroups)) {
1155 $this->tabs['FieldSelection'] = array(
1156 'title' => ts('Columns'),
1157 'tpl' => 'FieldSelection',
1158 'div_label' => 'col-groups',
1159 );
1160
1161 // Note this assignment is only really required in buildForm. It is being 'over-called'
1162 // to reduce risk of being missed due to overridden functions.
1163 $this->assign('tabs', $this->tabs);
1164 }
1165
6a488035
TO
1166 $this->assign('colGroups', $colGroups);
1167 }
1168
50951061
EM
1169 /**
1170 * Add filters to report.
1171 */
00be9182 1172 public function addFilters() {
7607643a 1173 $filters = $filterGroups = array();
6a488035 1174 $count = 1;
69719a15 1175
6a488035 1176 foreach ($this->_filters as $table => $attributes) {
69719a15
EM
1177 if (isset($this->_columns[$table]['group_title'])) {
1178 // The presence of 'group_title' is secret code for 'is_a_custom_table'
1179 // which magically means to 'display in an accordian'
1180 // here we make this explicit.
1181 $filterGroups[$table] = array(
1182 'group_title' => $this->_columns[$table]['group_title'],
1183 'use_accordian_for_field_selection' => TRUE,
1184
1185 );
1186 }
6a488035
TO
1187 foreach ($attributes as $fieldName => $field) {
1188 // get ready with option value pair
1b36206c 1189 // @ 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
1190 // would be useful
1191 $operations = $this->getOperationPair(
8f1445ea 1192 CRM_Utils_Array::value('operatorType', $field),
1b36206c 1193 $fieldName);
6a488035
TO
1194
1195 $filters[$table][$fieldName] = $field;
1196
1197 switch (CRM_Utils_Array::value('operatorType', $field)) {
1198 case CRM_Report_Form::OP_MONTH:
9d72cede
EM
1199 if (!array_key_exists('options', $field) ||
1200 !is_array($field['options']) || empty($field['options'])
1201 ) {
6a488035
TO
1202 // If there's no option list for this filter, define one.
1203 $field['options'] = array(
1204 1 => ts('January'),
1205 2 => ts('February'),
1206 3 => ts('March'),
1207 4 => ts('April'),
1208 5 => ts('May'),
1209 6 => ts('June'),
1210 7 => ts('July'),
1211 8 => ts('August'),
1212 9 => ts('September'),
1213 10 => ts('October'),
1214 11 => ts('November'),
1215 12 => ts('December'),
1216 );
1217 // Add this option list to this column _columns. This is
1218 // required so that filter statistics show properly.
1219 $this->_columns[$table]['filters'][$fieldName]['options'] = $field['options'];
1220 }
160d32e1
E
1221 case CRM_Report_Form::OP_MULTISELECT:
1222 case CRM_Report_Form::OP_MULTISELECT_SEPARATOR:
6a488035 1223 // assume a multi-select field
9d72cede
EM
1224 if (!empty($field['options']) ||
1225 $fieldName == 'state_province_id' || $fieldName == 'county_id'
1226 ) {
6a488035
TO
1227 $element = $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations);
1228 if (count($operations) <= 1) {
1229 $element->freeze();
1230 }
9d72cede
EM
1231 if ($fieldName == 'state_province_id' ||
1232 $fieldName == 'county_id'
1233 ) {
1234 $this->addChainSelect($fieldName . '_value', array(
86bd39be
TO
1235 'multiple' => TRUE,
1236 'label' => NULL,
21dfd5f5 1237 'class' => 'huge',
86bd39be 1238 ));
c927c151
CW
1239 }
1240 else {
1241 $this->addElement('select', "{$fieldName}_value", NULL, $field['options'], array(
1242 'style' => 'min-width:250px',
2107cde9 1243 'class' => 'crm-select2 huge',
c927c151
CW
1244 'multiple' => TRUE,
1245 'placeholder' => ts('- select -'),
1246 ));
1247 }
6a488035
TO
1248 }
1249 break;
1250
160d32e1 1251 case CRM_Report_Form::OP_SELECT:
6a488035
TO
1252 // assume a select field
1253 $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations);
9d72cede 1254 if (!empty($field['options'])) {
5a9a44d9 1255 $this->addElement('select', "{$fieldName}_value", NULL, $field['options']);
9d72cede 1256 }
6a488035
TO
1257 break;
1258
2107cde9
CW
1259 case CRM_Report_Form::OP_ENTITYREF:
1260 $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations);
1261 $this->setEntityRefDefaults($field, $table);
1262 $this->addEntityRef("{$fieldName}_value", NULL, $field['attributes']);
1263 break;
1264
160d32e1 1265 case CRM_Report_Form::OP_DATE:
6a488035 1266 // build datetime fields
ccc29f8e 1267 CRM_Core_Form_Date::buildDateRange($this, $fieldName, $count, '_from', '_to', ts('From:'), FALSE, $operations);
6a488035
TO
1268 $count++;
1269 break;
1270
160d32e1 1271 case CRM_Report_Form::OP_DATETIME:
6a488035 1272 // build datetime fields
ccc29f8e 1273 CRM_Core_Form_Date::buildDateRange($this, $fieldName, $count, '_from', '_to', ts('From:'), FALSE, $operations, 'searchDate', TRUE);
6a488035
TO
1274 $count++;
1275 break;
1276
160d32e1
E
1277 case CRM_Report_Form::OP_INT:
1278 case CRM_Report_Form::OP_FLOAT:
6a488035
TO
1279 // and a min value input box
1280 $this->add('text', "{$fieldName}_min", ts('Min'));
1281 // and a max value input box
1282 $this->add('text', "{$fieldName}_max", ts('Max'));
1283 default:
1284 // default type is string
1285 $this->addElement('select', "{$fieldName}_op", ts('Operator:'), $operations,
1286 array('onchange' => "return showHideMaxMinVal( '$fieldName', this.value );")
1287 );
1288 // we need text box for value input
2107cde9 1289 $this->add('text', "{$fieldName}_value", NULL, array('class' => 'huge'));
6a488035
TO
1290 break;
1291 }
1292 }
1293 }
0b62c1ab
EM
1294 if (!empty($filters)) {
1295 $this->tabs['Filters'] = array(
1296 'title' => ts('Filters'),
1297 'tpl' => 'Filters',
1298 'div_label' => 'set-filters',
1299 );
1300 }
6a488035 1301 $this->assign('filters', $filters);
69719a15 1302 $this->assign('filterGroups', $filterGroups);
6a488035
TO
1303 }
1304
0b62c1ab
EM
1305 /**
1306 * Function to assign the tabs to the template in the correct order.
1307 *
1308 * We want the tabs to wind up in this order (if not overridden).
1309 *
1310 * - Field Selection
1311 * - Group Bys
1312 * - Order Bys
1313 * - Other Options
1314 * - Filters
1315 */
1316 protected function assignTabs() {
1317 $order = array(
1318 'FieldSelection',
1319 'GroupBy',
1320 'OrderBy',
1321 'ReportOptions',
1322 'Filters',
1323 );
1324 $order = array_intersect_key(array_fill_keys($order, 1), $this->tabs);
1325 $order = array_merge($order, $this->tabs);
1326 $this->assign('tabs', $order);
1327 }
1328
02d451ab
EM
1329 /**
1330 * The intent is to add a tab for developers to view the sql.
1331 *
1332 * Currently using dpm.
1333 *
1334 * @param string $sql
1335 */
15d9e604 1336 public function addToDeveloperTab($sql) {
02d451ab
EM
1337 if (!CRM_Core_Permission::check('view report sql')) {
1338 return;
1339 }
1340 $this->tabs['Developer'] = array(
1341 'title' => ts('Developer'),
1342 'tpl' => 'Developer',
1343 'div_label' => 'set-developer',
1344 );
1345
1346 $this->assignTabs();
55f71fa7 1347 $this->sqlArray[] = $sql;
43c1fa19 1348 foreach ($this->sqlArray as $sql) {
1349 foreach (array('LEFT JOIN') as $term) {
1350 $sql = str_replace($term, '<br>&nbsp&nbsp' . $term, $sql);
1351 }
1352 foreach (array('FROM', 'WHERE', 'GROUP BY', 'ORDER BY', 'LIMIT', ';') as $term) {
1353 $sql = str_replace($term, '<br><br>' . $term, $sql);
1354 }
1355 $this->sqlFormattedArray[] = $sql;
1356 $this->assign('sql', implode(';<br><br><br><br>', $this->sqlFormattedArray));
02d451ab 1357 }
02d451ab
EM
1358 }
1359
0b62c1ab 1360 /**
50951061
EM
1361 * Add options defined in $this->_options to the report.
1362 */
00be9182 1363 public function addOptions() {
6a488035
TO
1364 if (!empty($this->_options)) {
1365 // FIXME: For now lets build all elements as checkboxes.
1366 // Once we clear with the format we can build elements based on type
1367
6a488035 1368 foreach ($this->_options as $fieldName => $field) {
52b41f2f
FG
1369 $options = array();
1370
6a488035
TO
1371 if ($field['type'] == 'select') {
1372 $this->addElement('select', "{$fieldName}", $field['title'], $field['options']);
1373 }
4c9b6178 1374 elseif ($field['type'] == 'checkbox') {
6a488035 1375 $options[$field['title']] = $fieldName;
52634dad
DS
1376 $this->addCheckBox($fieldName, NULL,
1377 $options, NULL,
1378 NULL, NULL, NULL, $this->_fourColumnAttribute
1379 );
6a488035
TO
1380 }
1381 }
6a488035 1382 }
0f8c6e58 1383 if (!empty($this->_options) &&
1384 (!$this->_id
1385 || ($this->_id && CRM_Report_BAO_ReportInstance::contactCanAdministerReport($this->_id)))
1386 ) {
0b62c1ab
EM
1387 $this->tabs['ReportOptions'] = array(
1388 'title' => ts('Display Options'),
1389 'tpl' => 'ReportOptions',
1390 'div_label' => 'other-options',
1391 );
1392 }
52634dad 1393 $this->assign('otherOptions', $this->_options);
6a488035
TO
1394 }
1395
50951061
EM
1396 /**
1397 * Add chart options to the report.
1398 */
00be9182 1399 public function addChartOptions() {
6a488035 1400 if (!empty($this->_charts)) {
22b67281 1401 $this->addElement('select', "charts", ts('Chart'), $this->_charts);
6a488035
TO
1402 $this->assign('charts', $this->_charts);
1403 $this->addElement('submit', $this->_chartButtonName, ts('View'));
1404 }
1405 }
1406
50951061
EM
1407 /**
1408 * Add group by options to the report.
1409 */
00be9182 1410 public function addGroupBys() {
6a488035
TO
1411 $options = $freqElements = array();
1412
1413 foreach ($this->_columns as $tableName => $table) {
1414 if (array_key_exists('group_bys', $table)) {
1415 foreach ($table['group_bys'] as $fieldName => $field) {
c3fdd2b7 1416 if (!empty($field) && empty($field['no_display'])) {
6a488035 1417 $options[$field['title']] = $fieldName;
a7488080 1418 if (!empty($field['frequency'])) {
6a488035
TO
1419 $freqElements[$field['title']] = $fieldName;
1420 }
1421 }
1422 }
1423 }
1424 }
1425 $this->addCheckBox("group_bys", ts('Group by columns'), $options, NULL,
1426 NULL, NULL, NULL, $this->_fourColumnAttribute
1427 );
1428 $this->assign('groupByElements', $options);
0b62c1ab
EM
1429 if (!empty($options)) {
1430 $this->tabs['GroupBy'] = array(
1431 'title' => ts('Grouping'),
1432 'tpl' => 'GroupBy',
1433 'div_label' => 'group-by-elements',
1434 );
1435 }
6a488035
TO
1436
1437 foreach ($freqElements as $name) {
1438 $this->addElement('select', "group_bys_freq[$name]",
1439 ts('Frequency'), $this->_groupByDateFreq
1440 );
1441 }
1442 }
1443
0b62c1ab
EM
1444 /**
1445 * Add data for order by tab.
1446 */
00be9182 1447 public function addOrderBys() {
6a488035
TO
1448 $options = array();
1449 foreach ($this->_columns as $tableName => $table) {
1450
0b62c1ab 1451 // Report developer may define any column to order by; include these as order-by options.
6a488035
TO
1452 if (array_key_exists('order_bys', $table)) {
1453 foreach ($table['order_bys'] as $fieldName => $field) {
1454 if (!empty($field)) {
1455 $options[$fieldName] = $field['title'];
1456 }
1457 }
1458 }
1459
971d41b1
CW
1460 // Add searchable custom fields as order-by options, if so requested
1461 // (These are already indexed, so allowing to order on them is cheap.)
6a488035 1462
9d72cede
EM
1463 if ($this->_autoIncludeIndexedFieldsAsOrderBys &&
1464 array_key_exists('extends', $table) && !empty($table['extends'])
1465 ) {
6a488035 1466 foreach ($table['fields'] as $fieldName => $field) {
a7488080 1467 if (empty($field['no_display'])) {
6a488035
TO
1468 $options[$fieldName] = $field['title'];
1469 }
1470 }
1471 }
1472 }
1473
1474 asort($options);
1475
1476 $this->assign('orderByOptions', $options);
0b62c1ab
EM
1477 if (!empty($options)) {
1478 $this->tabs['OrderBy'] = array(
1479 'title' => ts('Sorting'),
1480 'tpl' => 'OrderBy',
cd732070 1481 'div_label' => 'order-by-elements',
0b62c1ab
EM
1482 );
1483 }
6a488035
TO
1484
1485 if (!empty($options)) {
1486 $options = array(
971d41b1
CW
1487 '-' => ' - none - ',
1488 ) + $options;
6a488035
TO
1489 for ($i = 1; $i <= 5; $i++) {
1490 $this->addElement('select', "order_bys[{$i}][column]", ts('Order by Column'), $options);
9d72cede 1491 $this->addElement('select', "order_bys[{$i}][order]", ts('Order by Order'), array(
ccc29f8e 1492 'ASC' => ts('Ascending'),
1493 'DESC' => ts('Descending'),
86bd39be 1494 ));
6a488035 1495 $this->addElement('checkbox', "order_bys[{$i}][section]", ts('Order by Section'), FALSE, array('id' => "order_by_section_$i"));
5895cba0 1496 $this->addElement('checkbox', "order_bys[{$i}][pageBreak]", ts('Page Break'), FALSE, array('id' => "order_by_pagebreak_$i"));
6a488035
TO
1497 }
1498 }
1499 }
1500
0b62c1ab 1501 /**
07f44165 1502 * This adds the tab referred to as Title and Format, rendered through Instance.tpl.
0b62c1ab 1503 *
07f44165
EM
1504 * @todo call this tab into the report template in the same way as OrderBy etc, ie
1505 * by adding a description of the tab to $this->tabs, causing the tab to be added in
1506 * Criteria.tpl.
0b62c1ab 1507 */
00be9182 1508 public function buildInstanceAndButtons() {
6a488035 1509 CRM_Report_Form_Instance::buildForm($this);
87ecd5b7 1510 $this->_actionButtonName = $this->getButtonName('submit');
1511 $this->addTaskMenu($this->getActions($this->_id));
6a488035 1512
87ecd5b7 1513 $this->assign('instanceForm', $this->_instanceForm);
6a488035 1514
8a5c4609
DG
1515 // CRM-16274 Determine if user has 'edit all contacts' or equivalent
1516 $permission = CRM_Core_Permission::getPermission();
1517 if ($permission == CRM_Core_Permission::EDIT &&
9d72cede
EM
1518 $this->_add2groupSupported
1519 ) {
6a488035 1520 $this->addElement('select', 'groups', ts('Group'),
9d72cede
EM
1521 array('' => ts('Add Contacts to Group')) +
1522 CRM_Core_PseudoConstant::nestedGroup(),
9597c394 1523 array('class' => 'crm-select2 crm-action-menu fa-plus huge')
6a488035
TO
1524 );
1525 $this->assign('group', TRUE);
1526 }
1527
24431f7b 1528 $this->addElement('submit', $this->_groupButtonName, '', array('style' => 'display: none;'));
6a488035
TO
1529
1530 $this->addChartOptions();
87ecd5b7 1531 $showResultsLabel = $this->getResultsLabel();
6a488035
TO
1532 $this->addButtons(array(
1533 array(
1534 'type' => 'submit',
87ecd5b7 1535 'name' => $showResultsLabel,
6a488035
TO
1536 'isDefault' => TRUE,
1537 ),
1538 )
1539 );
1540 }
1541
87ecd5b7 1542 /**
1543 * Has this form been submitted already?
1544 *
1545 * @return bool
1546 */
1547 public function resultsDisplayed() {
1548 $buttonName = $this->controller->getButtonName();
1549 return ($buttonName || $this->_outputMode);
1550 }
1551
1552 /**
1553 * Get the actions for this report instance.
1554 *
1555 * @param int $instanceId
1556 *
1557 * @return array
1558 */
1559 protected function getActions($instanceId) {
44543184 1560 $actions = CRM_Report_BAO_ReportInstance::getActionMetadata();
87ecd5b7 1561 if (empty($instanceId)) {
d0c1b07a 1562 $actions['report_instance.save'] = array(
1563 'title' => ts('Create Report'),
1564 'data' => array(
1565 'is_confirm' => TRUE,
1566 'confirm_title' => ts('Create Report'),
1567 'confirm_refresh_fields' => json_encode(array(
1568 'title' => array('selector' => '.crm-report-instanceForm-form-block-title', 'prepend' => ''),
1569 'description' => array('selector' => '.crm-report-instanceForm-form-block-description', 'prepend' => ''),
1570 )),
1571 ),
1572 );
87ecd5b7 1573 }
1574
44543184 1575 if (!$this->_csvSupported) {
1576 unset($actions['report_instance.csv']);
87ecd5b7 1577 }
1578
1579 return $actions;
1580 }
1581
0b62c1ab
EM
1582 /**
1583 * Main build form function.
1584 */
00be9182 1585 public function buildQuickForm() {
6a488035
TO
1586 $this->addColumns();
1587
1588 $this->addFilters();
1589
1590 $this->addOptions();
1591
1592 $this->addGroupBys();
1593
1594 $this->addOrderBys();
1595
1596 $this->buildInstanceAndButtons();
1597
0b62c1ab 1598 // Add form rule for report.
6a488035 1599 if (is_callable(array(
9d72cede 1600 $this,
21dfd5f5 1601 'formRule',
9d72cede 1602 ))) {
6a488035
TO
1603 $this->addFormRule(array(get_class($this), 'formRule'), $this);
1604 }
0b62c1ab 1605 $this->assignTabs();
6a488035
TO
1606 }
1607
74cf4551 1608 /**
317a8023 1609 * A form rule function for custom data.
1610 *
1611 * The rule ensures that fields selected in group_by if any) should only be the ones
1612 * present in display/select fields criteria;
4f1f1f2a 1613 * note: works if and only if any custom field selected in group_by.
0b62c1ab 1614 *
537c70b8 1615 * @param array $fields
74cf4551
EM
1616 * @param array $ignoreFields
1617 *
1618 * @return array
1619 */
00be9182 1620 public function customDataFormRule($fields, $ignoreFields = array()) {
6a488035 1621 $errors = array();
9d72cede
EM
1622 if (!empty($this->_customGroupExtends) && $this->_customGroupGroupBy &&
1623 !empty($fields['group_bys'])
1624 ) {
6a488035 1625 foreach ($this->_columns as $tableName => $table) {
9d72cede
EM
1626 if ((substr($tableName, 0, 13) == 'civicrm_value' ||
1627 substr($tableName, 0, 12) == 'custom_value') &&
1628 !empty($this->_columns[$tableName]['fields'])
1629 ) {
6a488035
TO
1630 foreach ($this->_columns[$tableName]['fields'] as $fieldName => $field) {
1631 if (array_key_exists($fieldName, $fields['group_bys']) &&
1632 !array_key_exists($fieldName, $fields['fields'])
1633 ) {
1634 $errors['fields'] = "Please make sure fields selected in 'Group by Columns' section are also selected in 'Display Columns' section.";
1635 }
1636 elseif (array_key_exists($fieldName, $fields['group_bys'])) {
1637 foreach ($fields['fields'] as $fld => $val) {
9d72cede
EM
1638 if (!array_key_exists($fld, $fields['group_bys']) &&
1639 !in_array($fld, $ignoreFields)
1640 ) {
6a488035
TO
1641 $errors['fields'] = "Please ensure that fields selected in 'Display Columns' are also selected in 'Group by Columns' section.";
1642 }
1643 }
1644 }
1645 }
1646 }
1647 }
1648 }
1649 return $errors;
1650 }
1651
74cf4551 1652 /**
0b62c1ab
EM
1653 * Get operators to display on form.
1654 *
1655 * Note: $fieldName param allows inheriting class to build operationPairs specific to a field.
1656 *
74cf4551 1657 * @param string $type
8a925f33 1658 * @param string $fieldName
74cf4551
EM
1659 *
1660 * @return array
1661 */
00be9182 1662 public function getOperationPair($type = "string", $fieldName = NULL) {
6a488035
TO
1663 // FIXME: At some point we should move these key-val pairs
1664 // to option_group and option_value table.
6a488035 1665 switch ($type) {
160d32e1
E
1666 case CRM_Report_Form::OP_INT:
1667 case CRM_Report_Form::OP_FLOAT:
bed98343 1668
6c552737 1669 $result = array(
bc3f7f04 1670 'lte' => ts('Is less than or equal to'),
6a488035
TO
1671 'gte' => ts('Is greater than or equal to'),
1672 'bw' => ts('Is between'),
1673 'eq' => ts('Is equal to'),
1674 'lt' => ts('Is less than'),
1675 'gt' => ts('Is greater than'),
1676 'neq' => ts('Is not equal to'),
1677 'nbw' => ts('Is not between'),
1678 'nll' => ts('Is empty (Null)'),
1679 'nnll' => ts('Is not empty (Null)'),
1680 );
6c552737 1681 return $result;
6a488035 1682
160d32e1 1683 case CRM_Report_Form::OP_SELECT:
6c552737 1684 $result = array(
bc3f7f04 1685 'eq' => ts('Is equal to'),
1686 );
6c552737 1687 return $result;
6a488035 1688
160d32e1
E
1689 case CRM_Report_Form::OP_MONTH:
1690 case CRM_Report_Form::OP_MULTISELECT:
2107cde9 1691 case CRM_Report_Form::OP_ENTITYREF:
bed98343 1692
6c552737 1693 $result = array(
bc3f7f04 1694 'in' => ts('Is one of'),
6a488035
TO
1695 'notin' => ts('Is not one of'),
1696 );
6c552737 1697 return $result;
6a488035 1698
160d32e1 1699 case CRM_Report_Form::OP_DATE:
bed98343 1700
6c552737 1701 $result = array(
bc3f7f04 1702 'nll' => ts('Is empty (Null)'),
6a488035
TO
1703 'nnll' => ts('Is not empty (Null)'),
1704 );
6c552737 1705 return $result;
6a488035 1706
160d32e1 1707 case CRM_Report_Form::OP_MULTISELECT_SEPARATOR:
6a488035
TO
1708 // use this operator for the values, concatenated with separator. For e.g if
1709 // multiple options for a column is stored as ^A{val1}^A{val2}^A
6c552737 1710 $result = array(
bc3f7f04 1711 'mhas' => ts('Is one of'),
67788963 1712 'mnot' => ts('Is not one of'),
bc3f7f04 1713 );
6c552737 1714 return $result;
6a488035
TO
1715
1716 default:
1717 // type is string
6c552737 1718 $result = array(
bc3f7f04 1719 'has' => ts('Contains'),
6a488035
TO
1720 'sw' => ts('Starts with'),
1721 'ew' => ts('Ends with'),
1722 'nhas' => ts('Does not contain'),
1723 'eq' => ts('Is equal to'),
1724 'neq' => ts('Is not equal to'),
1725 'nll' => ts('Is empty (Null)'),
1726 'nnll' => ts('Is not empty (Null)'),
1727 );
6c552737 1728 return $result;
6a488035
TO
1729 }
1730 }
1731
07f44165
EM
1732 /**
1733 * Build the tag filter field to display on the filters tab.
1734 */
00be9182 1735 public function buildTagFilter() {
ed795723 1736 $contactTags = CRM_Core_BAO_Tag::getTags($this->_tagFilterTable);
6a488035
TO
1737 if (!empty($contactTags)) {
1738 $this->_columns['civicrm_tag'] = array(
1739 'dao' => 'CRM_Core_DAO_Tag',
9d72cede
EM
1740 'filters' => array(
1741 'tagid' => array(
6a488035
TO
1742 'name' => 'tag_id',
1743 'title' => ts('Tag'),
8ee006e7 1744 'type' => CRM_Utils_Type::T_INT,
6a488035
TO
1745 'tag' => TRUE,
1746 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
1747 'options' => $contactTags,
1748 ),
1749 ),
1750 );
1751 }
1752 }
1753
688d37c6 1754 /**
0b62c1ab 1755 * Adds group filters to _columns (called from _Construct).
6a488035 1756 */
00be9182 1757 public function buildGroupFilter() {
6a488035 1758 $this->_columns['civicrm_group']['filters'] = array(
9d72cede 1759 'gid' => array(
6a488035
TO
1760 'name' => 'group_id',
1761 'title' => ts('Group'),
8ee006e7 1762 'type' => CRM_Utils_Type::T_INT,
6a488035
TO
1763 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
1764 'group' => TRUE,
16e2e80c 1765 'options' => CRM_Core_PseudoConstant::nestedGroup(),
6a488035
TO
1766 ),
1767 );
1768 if (empty($this->_columns['civicrm_group']['dao'])) {
1769 $this->_columns['civicrm_group']['dao'] = 'CRM_Contact_DAO_GroupContact';
1770 }
1771 if (empty($this->_columns['civicrm_group']['alias'])) {
1772 $this->_columns['civicrm_group']['alias'] = 'cgroup';
1773 }
1774 }
1775
74cf4551 1776 /**
07f44165
EM
1777 * Get SQL operator from form text version.
1778 *
74cf4551
EM
1779 * @param string $operator
1780 *
1781 * @return string
1782 */
00be9182 1783 public function getSQLOperator($operator = "like") {
6a488035
TO
1784 switch ($operator) {
1785 case 'eq':
1786 return '=';
1787
1788 case 'lt':
1789 return '<';
1790
1791 case 'lte':
1792 return '<=';
1793
1794 case 'gt':
1795 return '>';
1796
1797 case 'gte':
1798 return '>=';
1799
1800 case 'ne':
1801 case 'neq':
1802 return '!=';
1803
1804 case 'nhas':
1805 return 'NOT LIKE';
1806
1807 case 'in':
1808 return 'IN';
1809
1810 case 'notin':
1811 return 'NOT IN';
1812
1813 case 'nll':
1814 return 'IS NULL';
1815
1816 case 'nnll':
1817 return 'IS NOT NULL';
1818
1819 default:
1820 // type is string
1821 return 'LIKE';
1822 }
1823 }
1824
74cf4551 1825 /**
f587aa17 1826 * Generate where clause.
07f44165 1827 *
8a925f33 1828 * This can be overridden in reports for special treatment of a field
f587aa17 1829 *
537c70b8 1830 * @param array $field Field specifications
8a925f33 1831 * @param string $op Query operator (not an exact match to sql)
f587aa17
EM
1832 * @param mixed $value
1833 * @param float $min
1834 * @param float $max
74cf4551
EM
1835 *
1836 * @return null|string
1837 */
8a925f33 1838 public function whereClause(&$field, $op, $value, $min, $max) {
6a488035
TO
1839
1840 $type = CRM_Utils_Type::typeToString(CRM_Utils_Array::value('type', $field));
8ee006e7
WA
1841
1842 // CRM-18010: Ensure type of each report filters
1843 if (!$type) {
1844 trigger_error('Type is not defined for field ' . $field['name'], E_USER_WARNING);
1845 }
6a488035
TO
1846 $clause = NULL;
1847
1848 switch ($op) {
1849 case 'bw':
1850 case 'nbw':
1851 if (($min !== NULL && strlen($min) > 0) ||
1852 ($max !== NULL && strlen($max) > 0)
1853 ) {
6a488035
TO
1854 $clauses = array();
1855 if ($min) {
6621df60 1856 $min = CRM_Utils_Type::escape($min, $type);
6a488035
TO
1857 if ($op == 'bw') {
1858 $clauses[] = "( {$field['dbAlias']} >= $min )";
1859 }
1860 else {
cd87f963 1861 $clauses[] = "( {$field['dbAlias']} < $min OR {$field['dbAlias']} IS NULL )";
6a488035
TO
1862 }
1863 }
1864 if ($max) {
6621df60 1865 $max = CRM_Utils_Type::escape($max, $type);
6a488035
TO
1866 if ($op == 'bw') {
1867 $clauses[] = "( {$field['dbAlias']} <= $max )";
1868 }
1869 else {
1870 $clauses[] = "( {$field['dbAlias']} > $max )";
1871 }
1872 }
1873
1874 if (!empty($clauses)) {
1875 if ($op == 'bw') {
1876 $clause = implode(' AND ', $clauses);
1877 }
1878 else {
cd87f963 1879 $clause = '(' . implode('OR', $clauses) . ')';
6a488035
TO
1880 }
1881 }
1882 }
1883 break;
1884
1885 case 'has':
1886 case 'nhas':
1887 if ($value !== NULL && strlen($value) > 0) {
1888 $value = CRM_Utils_Type::escape($value, $type);
1889 if (strpos($value, '%') === FALSE) {
1890 $value = "'%{$value}%'";
1891 }
1892 else {
1893 $value = "'{$value}'";
1894 }
29fc2b79 1895 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1896 $clause = "( {$field['dbAlias']} $sqlOP $value )";
1897 }
1898 break;
1899
1900 case 'in':
1901 case 'notin':
43c1fa19 1902 if ((is_string($value) || is_numeric($value)) && strlen($value)) {
2107cde9
CW
1903 $value = explode(',', $value);
1904 }
6a488035 1905 if ($value !== NULL && is_array($value) && count($value) > 0) {
29fc2b79 1906 $sqlOP = $this->getSQLOperator($op);
9d72cede
EM
1907 if (CRM_Utils_Array::value('type', $field) ==
1908 CRM_Utils_Type::T_STRING
1909 ) {
f587aa17 1910 //cycle through selections and escape values
6a488035
TO
1911 foreach ($value as $key => $selection) {
1912 $value[$key] = CRM_Utils_Type::escape($selection, $type);
1913 }
971d41b1
CW
1914 $clause
1915 = "( {$field['dbAlias']} $sqlOP ( '" . implode("' , '", $value) .
9d72cede 1916 "') )";
6a488035
TO
1917 }
1918 else {
1919 // for numerical values
9d72cede
EM
1920 $clause = "{$field['dbAlias']} $sqlOP (" . implode(', ', $value) .
1921 ")";
6a488035
TO
1922 }
1923 if ($op == 'notin') {
1924 $clause = "( " . $clause . " OR {$field['dbAlias']} IS NULL )";
1925 }
1926 else {
1927 $clause = "( " . $clause . " )";
1928 }
1929 }
1930 break;
1931
1932 case 'mhas':
67788963 1933 case 'mnot':
05839ccc 1934 // multiple has or multiple not
67788963 1935 if ($value !== NULL && count($value) > 0) {
f4fd6082 1936 $value = CRM_Utils_Type::escapeAll($value, $type);
1937 $operator = $op == 'mnot' ? 'NOT' : '';
228753d3 1938 $regexp = "([[:cntrl:]]|^)" . implode('([[:cntrl:]]|$)|([[:cntrl:]]|^)', (array) $value) . "([[:cntrl:]]|$)";
f4fd6082 1939 $clause = "{$field['dbAlias']} {$operator} REGEXP '{$regexp}'";
67788963
J
1940 }
1941 break;
6a488035
TO
1942
1943 case 'sw':
1944 case 'ew':
1945 if ($value !== NULL && strlen($value) > 0) {
1946 $value = CRM_Utils_Type::escape($value, $type);
1947 if (strpos($value, '%') === FALSE) {
1948 if ($op == 'sw') {
1949 $value = "'{$value}%'";
1950 }
1951 else {
1952 $value = "'%{$value}'";
1953 }
1954 }
1955 else {
1956 $value = "'{$value}'";
1957 }
29fc2b79 1958 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1959 $clause = "( {$field['dbAlias']} $sqlOP $value )";
1960 }
1961 break;
1962
1963 case 'nll':
1964 case 'nnll':
29fc2b79 1965 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1966 $clause = "( {$field['dbAlias']} $sqlOP )";
1967 break;
1968
55a5250b 1969 case 'eq':
1970 case 'neq':
1971 case 'ne':
1972 //CRM-18457: some custom field passes value in array format against binary operator
1973 if (is_array($value) && count($value)) {
1974 $value = $value[0];
1975 }
1976
6a488035 1977 default:
55a5250b 1978 if ($value !== NULL && $value !== '') {
6a488035
TO
1979 if (isset($field['clause'])) {
1980 // FIXME: we not doing escape here. Better solution is to use two
1981 // different types - data-type and filter-type
0e6e8724 1982 $clause = $field['clause'];
6a488035 1983 }
55a5250b 1984 elseif (!is_array($value)) {
6a488035 1985 $value = CRM_Utils_Type::escape($value, $type);
29fc2b79 1986 $sqlOP = $this->getSQLOperator($op);
6a488035
TO
1987 if ($field['type'] == CRM_Utils_Type::T_STRING) {
1988 $value = "'{$value}'";
1989 }
1990 $clause = "( {$field['dbAlias']} $sqlOP $value )";
1991 }
1992 }
1993 break;
1994 }
1995
a7488080 1996 if (!empty($field['group']) && $clause) {
6a488035
TO
1997 $clause = $this->whereGroupClause($field, $value, $op);
1998 }
a7488080 1999 elseif (!empty($field['tag']) && $clause) {
6a488035
TO
2000 // not using left join in query because if any contact
2001 // belongs to more than one tag, results duplicate
2002 // entries.
2003 $clause = $this->whereTagClause($field, $value, $op);
2004 }
114a2c85 2005 elseif (!empty($field['membership_org']) && $clause) {
f587aa17 2006 $clause = $this->whereMembershipOrgClause($value, $op);
114a2c85
DG
2007 }
2008 elseif (!empty($field['membership_type']) && $clause) {
f587aa17 2009 $clause = $this->whereMembershipTypeClause($value, $op);
114a2c85 2010 }
6a488035
TO
2011 return $clause;
2012 }
2013
74cf4551 2014 /**
07f44165
EM
2015 * Get SQL where clause for a date field.
2016 *
100fef9d 2017 * @param string $fieldName
317a8023 2018 * @param string $relative
f587aa17
EM
2019 * @param string $from
2020 * @param string $to
07f44165
EM
2021 * @param string $type
2022 * @param string $fromTime
2023 * @param string $toTime
74cf4551
EM
2024 *
2025 * @return null|string
2026 */
971d41b1 2027 public function dateClause(
7d8c1168
TO
2028 $fieldName,
2029 $relative, $from, $to, $type = NULL, $fromTime = NULL, $toTime = NULL
6a488035
TO
2030 ) {
2031 $clauses = array();
160d32e1 2032 if (in_array($relative, array_keys($this->getOperationPair(CRM_Report_Form::OP_DATE)))) {
29fc2b79 2033 $sqlOP = $this->getSQLOperator($relative);
6a488035
TO
2034 return "( {$fieldName} {$sqlOP} )";
2035 }
2036
29fc2b79 2037 list($from, $to) = $this->getFromTo($relative, $from, $to, $fromTime, $toTime);
6a488035
TO
2038
2039 if ($from) {
2040 $from = ($type == CRM_Utils_Type::T_DATE) ? substr($from, 0, 8) : $from;
2041 $clauses[] = "( {$fieldName} >= $from )";
2042 }
2043
2044 if ($to) {
2045 $to = ($type == CRM_Utils_Type::T_DATE) ? substr($to, 0, 8) : $to;
2046 $clauses[] = "( {$fieldName} <= {$to} )";
2047 }
2048
2049 if (!empty($clauses)) {
2050 return implode(' AND ', $clauses);
2051 }
2052
2053 return NULL;
2054 }
9d72cede 2055
74cf4551 2056 /**
42b5c549
EM
2057 * Get values for from and to for date ranges.
2058 *
5e9221b7 2059 * @deprecated
2060 *
f587aa17
EM
2061 * @param bool $relative
2062 * @param string $from
2063 * @param string $to
9907633b
EM
2064 * @param string $fromTime
2065 * @param string $toTime
74cf4551
EM
2066 *
2067 * @return array
2068 */
5e9221b7 2069 public function getFromTo($relative, $from, $to, $fromTime = NULL, $toTime = NULL) {
2070 if (empty($toTime)) {
2071 // odd legacy behaviour to treat NULL as 'end of the day'
2072 // recommend updating reports to call CRM_Utils_Date::getFromTo
2073 //directly (default on the function is the actual default there).
2074 $toTime = '235959';
2075 }
7ec58d2b 2076 return CRM_Utils_Date::getFromTo($relative, $from, $to, $fromTime, $toTime);
6a488035
TO
2077 }
2078
74cf4551 2079 /**
4b62bc4f
EM
2080 * Alter display of rows.
2081 *
2082 * Iterate through the rows retrieved via SQL and make changes for display purposes,
2083 * such as rendering contacts as links.
2084 *
2085 * @param array $rows
2086 * Rows generated by SQL, with an array for each row.
74cf4551 2087 */
00be9182 2088 public function alterDisplay(&$rows) {
6a488035
TO
2089 }
2090
74cf4551 2091 /**
42b5c549
EM
2092 * Alter the way in which custom data fields are displayed.
2093 *
2094 * @param array $rows
74cf4551 2095 */
00be9182 2096 public function alterCustomDataDisplay(&$rows) {
6a488035
TO
2097 // custom code to alter rows having custom values
2098 if (empty($this->_customGroupExtends)) {
2099 return;
2100 }
2101
1dbed479 2102 $customFields = array();
6a488035
TO
2103 $customFieldIds = array();
2104 foreach ($this->_params['fields'] as $fieldAlias => $value) {
2105 if ($fieldId = CRM_Core_BAO_CustomField::getKeyID($fieldAlias)) {
2106 $customFieldIds[$fieldAlias] = $fieldId;
2107 }
2108 }
2109 if (empty($customFieldIds)) {
2110 return;
2111 }
2112
6a488035
TO
2113 // skip for type date and ContactReference since date format is already handled
2114 $query = "
28241a61 2115SELECT cg.table_name, cf.id
6a488035
TO
2116FROM civicrm_custom_field cf
2117INNER JOIN civicrm_custom_group cg ON cg.id = cf.custom_group_id
6a488035
TO
2118WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
2119 cg.is_active = 1 AND
2120 cf.is_active = 1 AND
2121 cf.is_searchable = 1 AND
2122 cf.data_type NOT IN ('ContactReference', 'Date') AND
2123 cf.id IN (" . implode(",", $customFieldIds) . ")";
2124
2125 $dao = CRM_Core_DAO::executeQuery($query);
2126 while ($dao->fetch()) {
28241a61 2127 $customFields[$dao->table_name . '_custom_' . $dao->id] = $dao->id;
6a488035
TO
2128 }
2129 $dao->free();
2130
2131 $entryFound = FALSE;
2132 foreach ($rows as $rowNum => $row) {
2133 foreach ($row as $tableCol => $val) {
2134 if (array_key_exists($tableCol, $customFields)) {
28241a61 2135 $rows[$rowNum][$tableCol] = CRM_Core_BAO_CustomField::displayValue($val, $customFields[$tableCol]);
6a488035
TO
2136 $entryFound = TRUE;
2137 }
2138 }
2139
2140 // skip looking further in rows, if first row itself doesn't
2141 // have the column we need
2142 if (!$entryFound) {
2143 break;
2144 }
2145 }
2146 }
2147
74cf4551 2148 /**
317a8023 2149 * Remove duplicate rows.
2150 *
2151 * @param array $rows
74cf4551 2152 */
00be9182 2153 public function removeDuplicates(&$rows) {
6a488035
TO
2154 if (empty($this->_noRepeats)) {
2155 return;
2156 }
2157 $checkList = array();
2158
2159 foreach ($rows as $key => $list) {
2160 foreach ($list as $colName => $colVal) {
8f1445ea 2161 if (array_key_exists($colName, $checkList) &&
9d72cede
EM
2162 $checkList[$colName] == $colVal
2163 ) {
6a488035
TO
2164 $rows[$key][$colName] = "";
2165 }
2166 if (in_array($colName, $this->_noRepeats)) {
2167 $checkList[$colName] = $colVal;
2168 }
2169 }
2170 }
2171 }
2172
74cf4551 2173 /**
317a8023 2174 * Fix subtotal display.
2175 *
2176 * @param array $row
2177 * @param array $fields
74cf4551
EM
2178 * @param bool $subtotal
2179 */
00be9182 2180 public function fixSubTotalDisplay(&$row, $fields, $subtotal = TRUE) {
6a488035
TO
2181 foreach ($row as $colName => $colVal) {
2182 if (in_array($colName, $fields)) {
6a488035
TO
2183 }
2184 elseif (isset($this->_columnHeaders[$colName])) {
2185 if ($subtotal) {
8c2959c8 2186 $row[$colName] = 'Subtotal';
6a488035
TO
2187 $subtotal = FALSE;
2188 }
2189 else {
2190 unset($row[$colName]);
2191 }
2192 }
2193 }
2194 }
2195
74cf4551 2196 /**
317a8023 2197 * Calculate grant total.
2198 *
2199 * @param array $rows
74cf4551
EM
2200 *
2201 * @return bool
2202 */
00be9182 2203 public function grandTotal(&$rows) {
c160fde8 2204 if (!$this->_rollup || count($rows) == 1) {
2205 return FALSE;
2206 }
2207
2208 $this->moveSummaryColumnsToTheRightHandSide();
2209
2210 if ($this->_limit && count($rows) >= self::ROW_COUNT_LIMIT) {
6a488035
TO
2211 return FALSE;
2212 }
8a2bee47 2213
2214 $this->rollupRow = array_pop($rows);
6a488035 2215
6a488035
TO
2216 foreach ($this->_columnHeaders as $fld => $val) {
2217 if (!in_array($fld, $this->_statFields)) {
2218 if (!$this->_grandFlag) {
658682cd 2219 $this->rollupRow[$fld] = ts('Grand Total');
6a488035
TO
2220 $this->_grandFlag = TRUE;
2221 }
2222 else {
8a2bee47 2223 $this->rollupRow[$fld] = "";
6a488035
TO
2224 }
2225 }
2226 }
2227
8a2bee47 2228 $this->assign('grandStat', $this->rollupRow);
6a488035
TO
2229 return TRUE;
2230 }
2231
74cf4551 2232 /**
317a8023 2233 * Format display output.
2234 *
2235 * @param array $rows
74cf4551
EM
2236 * @param bool $pager
2237 */
00be9182 2238 public function formatDisplay(&$rows, $pager = TRUE) {
6a488035
TO
2239 // set pager based on if any limit was applied in the query.
2240 if ($pager) {
2241 $this->setPager();
2242 }
2243
2244 // allow building charts if any
2245 if (!empty($this->_params['charts']) && !empty($rows)) {
2246 $this->buildChart($rows);
2247 $this->assign('chartEnabled', TRUE);
9d72cede
EM
2248 $this->_chartId = "{$this->_params['charts']}_" .
2249 ($this->_id ? $this->_id : substr(get_class($this), 16)) . '_' .
2250 session_id();
6a488035
TO
2251 $this->assign('chartId', $this->_chartId);
2252 }
2253
2254 // unset columns not to be displayed.
2255 foreach ($this->_columnHeaders as $key => $value) {
a7488080 2256 if (!empty($value['no_display'])) {
6a488035
TO
2257 unset($this->_columnHeaders[$key]);
2258 }
2259 }
2260
2261 // unset columns not to be displayed.
2262 if (!empty($rows)) {
2263 foreach ($this->_noDisplay as $noDisplayField) {
2264 foreach ($rows as $rowNum => $row) {
2265 unset($this->_columnHeaders[$noDisplayField]);
2266 }
2267 }
2268 }
2269
2270 // build array of section totals
2271 $this->sectionTotals();
2272
2273 // process grand-total row
2274 $this->grandTotal($rows);
2275
2276 // use this method for formatting rows for display purpose.
2277 $this->alterDisplay($rows);
2278 CRM_Utils_Hook::alterReportVar('rows', $rows, $this);
2279
2280 // use this method for formatting custom rows for display purpose.
2281 $this->alterCustomDataDisplay($rows);
2282 }
2283
74cf4551 2284 /**
317a8023 2285 * Build chart.
2286 *
2287 * @param array $rows
74cf4551 2288 */
00be9182 2289 public function buildChart(&$rows) {
6a488035
TO
2290 // override this method for building charts.
2291 }
2292
2293 // select() method below has been added recently (v3.3), and many of the report templates might
2294 // still be having their own select() method. We should fix them as and when encountered and move
2295 // towards generalizing the select() method below.
971d41b1
CW
2296
2297 /**
317a8023 2298 * Generate the SELECT clause and set class variable $_select.
971d41b1 2299 */
00be9182 2300 public function select() {
1f220d30 2301 $select = $this->_selectAliases = array();
534c4d20 2302 $this->storeGroupByArray();
6a488035
TO
2303
2304 foreach ($this->_columns as $tableName => $table) {
2305 if (array_key_exists('fields', $table)) {
2306 foreach ($table['fields'] as $fieldName => $field) {
2307 if ($tableName == 'civicrm_address') {
f38e3c19 2308 // deprecated, use $this->isTableSelected.
6a488035
TO
2309 $this->_addressField = TRUE;
2310 }
2311 if ($tableName == 'civicrm_email') {
2312 $this->_emailField = TRUE;
2313 }
2314 if ($tableName == 'civicrm_phone') {
2315 $this->_phoneField = TRUE;
2316 }
2317
9d72cede
EM
2318 if (!empty($field['required']) ||
2319 !empty($this->_params['fields'][$fieldName])
2320 ) {
6a488035
TO
2321
2322 // 1. In many cases we want select clause to be built in slightly different way
317a8023 2323 // for a particular field of a particular type.
6a488035 2324 // 2. This method when used should receive params by reference and modify $this->_columnHeaders
317a8023 2325 // as needed.
6a488035
TO
2326 $selectClause = $this->selectClause($tableName, 'fields', $fieldName, $field);
2327 if ($selectClause) {
2328 $select[] = $selectClause;
2329 continue;
2330 }
2331
2332 // include statistics columns only if set
4b885f84 2333 if (!empty($field['statistics']) && !empty($this->_groupByArray)) {
0a01dff9 2334 $select = $this->addStatisticsToSelect($field, $tableName, $fieldName, $select);
6a488035
TO
2335 }
2336 else {
81a22d3d 2337
2338 $selectClause = $this->getSelectClauseWithGroupConcatIfNotGroupedBy($tableName, $fieldName, $field);
2339 if ($selectClause) {
2340 $select[] = $selectClause;
2341 }
2342 else {
2343 $select = $this->addBasicFieldToSelect($tableName, $fieldName, $field, $select);
2344 }
6a488035
TO
2345 }
2346 }
2347 }
2348 }
2349
2350 // select for group bys
2351 if (array_key_exists('group_bys', $table)) {
2352 foreach ($table['group_bys'] as $fieldName => $field) {
2353
2354 if ($tableName == 'civicrm_address') {
2355 $this->_addressField = TRUE;
2356 }
2357 if ($tableName == 'civicrm_email') {
2358 $this->_emailField = TRUE;
2359 }
2360 if ($tableName == 'civicrm_phone') {
2361 $this->_phoneField = TRUE;
2362 }
2363 // 1. In many cases we want select clause to be built in slightly different way
317a8023 2364 // for a particular field of a particular type.
6a488035 2365 // 2. This method when used should receive params by reference and modify $this->_columnHeaders
317a8023 2366 // as needed.
6a488035
TO
2367 $selectClause = $this->selectClause($tableName, 'group_bys', $fieldName, $field);
2368 if ($selectClause) {
2369 $select[] = $selectClause;
2370 continue;
2371 }
2372
9d72cede
EM
2373 if (!empty($this->_params['group_bys']) &&
2374 !empty($this->_params['group_bys'][$fieldName]) &&
2375 !empty($this->_params['group_bys_freq'])
2376 ) {
6a488035
TO
2377 switch (CRM_Utils_Array::value($fieldName, $this->_params['group_bys_freq'])) {
2378 case 'YEARWEEK':
9d72cede
EM
2379 $select[] = "DATE_SUB({$field['dbAlias']}, INTERVAL WEEKDAY({$field['dbAlias']}) DAY) AS {$tableName}_{$fieldName}_start";
2380 $select[] = "YEARWEEK({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2381 $select[] = "WEEKOFYEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2382 $field['title'] = 'Week';
2383 break;
2384
2385 case 'YEAR':
9d72cede
EM
2386 $select[] = "MAKEDATE(YEAR({$field['dbAlias']}), 1) AS {$tableName}_{$fieldName}_start";
2387 $select[] = "YEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2388 $select[] = "YEAR({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2389 $field['title'] = 'Year';
2390 break;
2391
2392 case 'MONTH':
9d72cede
EM
2393 $select[] = "DATE_SUB({$field['dbAlias']}, INTERVAL (DAYOFMONTH({$field['dbAlias']})-1) DAY) as {$tableName}_{$fieldName}_start";
2394 $select[] = "MONTH({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2395 $select[] = "MONTHNAME({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2396 $field['title'] = 'Month';
2397 break;
2398
2399 case 'QUARTER':
9d72cede
EM
2400 $select[] = "STR_TO_DATE(CONCAT( 3 * QUARTER( {$field['dbAlias']} ) -2 , '/', '1', '/', YEAR( {$field['dbAlias']} ) ), '%m/%d/%Y') AS {$tableName}_{$fieldName}_start";
2401 $select[] = "QUARTER({$field['dbAlias']}) AS {$tableName}_{$fieldName}_subtotal";
2402 $select[] = "QUARTER({$field['dbAlias']}) AS {$tableName}_{$fieldName}_interval";
6a488035
TO
2403 $field['title'] = 'Quarter';
2404 break;
2405 }
2406 // for graphs and charts -
a7488080 2407 if (!empty($this->_params['group_bys_freq'][$fieldName])) {
6a488035 2408 $this->_interval = $field['title'];
971d41b1
CW
2409 $this->_columnHeaders["{$tableName}_{$fieldName}_start"]['title']
2410 = $field['title'] . ' Beginning';
6a488035
TO
2411 $this->_columnHeaders["{$tableName}_{$fieldName}_start"]['type'] = $field['type'];
2412 $this->_columnHeaders["{$tableName}_{$fieldName}_start"]['group_by'] = $this->_params['group_bys_freq'][$fieldName];
2413
7d7c50f9 2414 // just to make sure these values are transferred to rows.
6a488035
TO
2415 // since we 'll need them for calculation purpose,
2416 // e.g making subtotals look nicer or graphs
2417 $this->_columnHeaders["{$tableName}_{$fieldName}_interval"] = array('no_display' => TRUE);
2418 $this->_columnHeaders["{$tableName}_{$fieldName}_subtotal"] = array('no_display' => TRUE);
2419 }
2420 }
2421 }
2422 }
2423 }
2424
048387ef 2425 if (empty($select)) {
2426 // CRM-21412 Do not give fatal error on report when no fields selected
2427 $select = array(1);
2428 }
2429
1f220d30 2430 $this->_selectClauses = $select;
6a488035
TO
2431 $this->_select = "SELECT " . implode(', ', $select) . " ";
2432 }
2433
74cf4551 2434 /**
317a8023 2435 * Build select clause for a single field.
2436 *
100fef9d 2437 * @param string $tableName
317a8023 2438 * @param string $tableKey
100fef9d 2439 * @param string $fieldName
317a8023 2440 * @param string $field
74cf4551
EM
2441 *
2442 * @return bool
2443 */
00be9182 2444 public function selectClause(&$tableName, $tableKey, &$fieldName, &$field) {
81a22d3d 2445 if (!empty($field['pseudofield'])) {
2446 $alias = "{$tableName}_{$fieldName}";
2447 $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = CRM_Utils_Array::value('title', $field);
2448 $this->_columnHeaders["{$tableName}_{$fieldName}"]['type'] = CRM_Utils_Array::value('type', $field);
2449 $this->_columnHeaders["{$tableName}_{$fieldName}"]['dbAlias'] = CRM_Utils_Array::value('dbAlias', $field);
2450 $this->_selectAliases[] = $alias;
2451 return ' 1 as ' . $alias;
2452 }
6a488035
TO
2453 return FALSE;
2454 }
2455
317a8023 2456 /**
2457 * Build where clause.
2458 */
00be9182 2459 public function where() {
adfe2750 2460 $this->storeWhereHavingClauseArray();
2461
2462 if (empty($this->_whereClauses)) {
2463 $this->_where = "WHERE ( 1 ) ";
2464 $this->_having = "";
2465 }
2466 else {
2467 $this->_where = "WHERE " . implode(' AND ', $this->_whereClauses);
2468 }
2469
2470 if ($this->_aclWhere) {
2471 $this->_where .= " AND {$this->_aclWhere} ";
2472 }
2473
2474 if (!empty($this->_havingClauses)) {
2475 // use this clause to construct group by clause.
2476 $this->_having = "HAVING " . implode(' AND ', $this->_havingClauses);
2477 }
2478 }
2479
2480 /**
317a8023 2481 * Store Where clauses into an array.
2482 *
2483 * Breaking out this step makes over-riding more flexible as the clauses can be used in constructing a
adfe2750 2484 * temp table that may not be part of the final where clause or added
2485 * in other functions
2486 */
00be9182 2487 public function storeWhereHavingClauseArray() {
6a488035
TO
2488 foreach ($this->_columns as $tableName => $table) {
2489 if (array_key_exists('filters', $table)) {
2490 foreach ($table['filters'] as $fieldName => $field) {
d12de91c 2491 // respect pseudofield to filter spec so fields can be marked as
2492 // not to be handled here
9d72cede 2493 if (!empty($field['pseudofield'])) {
d12de91c 2494 continue;
2495 }
6a488035
TO
2496 $clause = NULL;
2497 if (CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_DATE) {
9d72cede
EM
2498 if (CRM_Utils_Array::value('operatorType', $field) ==
2499 CRM_Report_Form::OP_MONTH
2500 ) {
6a488035
TO
2501 $op = CRM_Utils_Array::value("{$fieldName}_op", $this->_params);
2502 $value = CRM_Utils_Array::value("{$fieldName}_value", $this->_params);
2503 if (is_array($value) && !empty($value)) {
971d41b1
CW
2504 $clause
2505 = "(month({$field['dbAlias']}) $op (" . implode(', ', $value) .
9d72cede 2506 '))';
6a488035
TO
2507 }
2508 }
2509 else {
2510 $relative = CRM_Utils_Array::value("{$fieldName}_relative", $this->_params);
9d72cede
EM
2511 $from = CRM_Utils_Array::value("{$fieldName}_from", $this->_params);
2512 $to = CRM_Utils_Array::value("{$fieldName}_to", $this->_params);
6a488035 2513 $fromTime = CRM_Utils_Array::value("{$fieldName}_from_time", $this->_params);
9d72cede
EM
2514 $toTime = CRM_Utils_Array::value("{$fieldName}_to_time", $this->_params);
2515 $clause = $this->dateClause($field['dbAlias'], $relative, $from, $to, $field['type'], $fromTime, $toTime);
6a488035
TO
2516 }
2517 }
2518 else {
2519 $op = CRM_Utils_Array::value("{$fieldName}_op", $this->_params);
2520 if ($op) {
2521 $clause = $this->whereClause($field,
adfe2750 2522 $op,
2523 CRM_Utils_Array::value("{$fieldName}_value", $this->_params),
2524 CRM_Utils_Array::value("{$fieldName}_min", $this->_params),
2525 CRM_Utils_Array::value("{$fieldName}_max", $this->_params)
6a488035
TO
2526 );
2527 }
2528 }
2529
2530 if (!empty($clause)) {
a7488080 2531 if (!empty($field['having'])) {
adfe2750 2532 $this->_havingClauses[] = $clause;
6a488035
TO
2533 }
2534 else {
adfe2750 2535 $this->_whereClauses[] = $clause;
6a488035
TO
2536 }
2537 }
2538 }
2539 }
2540 }
2541
6a488035 2542 }
9d72cede 2543
317a8023 2544 /**
2545 * Set output mode.
2546 */
00be9182 2547 public function processReportMode() {
182f5081 2548 $this->setOutputMode();
6a488035 2549
971d41b1
CW
2550 $this->_sendmail
2551 = CRM_Utils_Request::retrieve(
884605ca
DL
2552 'sendmail',
2553 'Boolean',
2554 CRM_Core_DAO::$_nullObject
2555 );
6a488035
TO
2556
2557 $this->_absoluteUrl = FALSE;
2558 $printOnly = FALSE;
2559 $this->assign('printOnly', FALSE);
2560
182f5081 2561 if ($this->_outputMode == 'print' ||
2562 ($this->_sendmail && !$this->_outputMode)
9d72cede 2563 ) {
6a488035
TO
2564 $this->assign('printOnly', TRUE);
2565 $printOnly = TRUE;
182f5081 2566 $this->addPaging = FALSE;
6a488035
TO
2567 $this->assign('outputMode', 'print');
2568 $this->_outputMode = 'print';
2569 if ($this->_sendmail) {
2570 $this->_absoluteUrl = TRUE;
9d72cede 2571 }
6a488035 2572 }
182f5081 2573 elseif ($this->_outputMode == 'pdf') {
6a488035 2574 $printOnly = TRUE;
182f5081 2575 $this->addPaging = FALSE;
6a488035
TO
2576 $this->_absoluteUrl = TRUE;
2577 }
182f5081 2578 elseif ($this->_outputMode == 'csv') {
6a488035 2579 $printOnly = TRUE;
6a488035 2580 $this->_absoluteUrl = TRUE;
182f5081 2581 $this->addPaging = FALSE;
6a488035 2582 }
182f5081 2583 elseif ($this->_outputMode == 'group') {
6a488035 2584 $this->assign('outputMode', 'group');
6a488035 2585 }
182f5081 2586 elseif ($this->_outputMode == 'create_report' && $this->_criteriaForm) {
6a488035 2587 $this->assign('outputMode', 'create_report');
6a488035 2588 }
182f5081 2589 elseif ($this->_outputMode == 'copy' && $this->_criteriaForm) {
2590 $this->_createNew = TRUE;
6a488035
TO
2591 }
2592
182f5081 2593 $this->assign('outputMode', $this->_outputMode);
2594 $this->assign('printOnly', $printOnly);
6a488035
TO
2595 // Get today's date to include in printed reports
2596 if ($printOnly) {
2597 $reportDate = CRM_Utils_Date::customFormat(date('Y-m-d H:i'));
2598 $this->assign('reportDate', $reportDate);
2599 }
2600 }
2601
9657ccf2 2602 /**
317a8023 2603 * Post Processing function for Form.
2604 *
2605 * postProcessCommon should be used to set other variables from input as the api accesses that function.
182f5081 2606 * This function is not accessed when the api calls the report.
9657ccf2 2607 */
00be9182 2608 public function beginPostProcess() {
c58f66e0 2609 $this->setParams($this->controller->exportValues($this->_name));
6a488035
TO
2610 if (empty($this->_params) &&
2611 $this->_force
2612 ) {
c58f66e0 2613 $this->setParams($this->_formValues);
6a488035
TO
2614 }
2615
2616 // hack to fix params when submitted from dashboard, CRM-8532
2617 // fields array is missing because form building etc is skipped
2618 // in dashboard mode for report
c58f66e0 2619 //@todo - this could be done in the dashboard no we have a setter
a7488080 2620 if (empty($this->_params['fields']) && !$this->_noFields) {
c58f66e0 2621 $this->setParams($this->_formValues);
6a488035
TO
2622 }
2623
6a488035 2624 $this->processReportMode();
182f5081 2625
2626 if ($this->_outputMode == 'save' || $this->_outputMode == 'copy') {
2627 $this->_createNew = ($this->_outputMode == 'copy');
182f5081 2628 CRM_Report_Form_Instance::postProcess($this);
2629 }
e3c612db 2630 if ($this->_outputMode == 'delete') {
2631 CRM_Report_BAO_ReportInstance::doFormDelete($this->_id, 'civicrm/report/list?reset=1', 'civicrm/report/list?reset=1');
2632 }
2633
a231baad 2634 $this->_formValues = $this->_params;
2635
c58f66e0
E
2636 $this->beginPostProcessCommon();
2637 }
2638
2639 /**
317a8023 2640 * BeginPostProcess function run in both report mode and non-report mode (api).
c58f66e0 2641 */
ccc29f8e 2642 public function beginPostProcessCommon() {
2643 }
6a488035 2644
74cf4551 2645 /**
317a8023 2646 * Build the report query.
2647 *
74cf4551
EM
2648 * @param bool $applyLimit
2649 *
2650 * @return string
2651 */
00be9182 2652 public function buildQuery($applyLimit = TRUE) {
43c1fa19 2653 $this->buildGroupTempTable();
6a488035
TO
2654 $this->select();
2655 $this->from();
2656 $this->customDataFrom();
f0384ec0 2657 $this->buildPermissionClause();
6a488035
TO
2658 $this->where();
2659 $this->groupBy();
2660 $this->orderBy();
2661
81a22d3d 2662 foreach ($this->unselectedOrderByColumns() as $alias => $field) {
2663 $clause = $this->getSelectClauseWithGroupConcatIfNotGroupedBy($field['table_name'], $field['name'], $field);
2664 if (!$clause) {
2665 $clause = "{$field['dbAlias']} as {$alias}";
2666 }
2667 $this->_select .= ", $clause ";
6a488035
TO
2668 }
2669
8cc574cf 2670 if ($applyLimit && empty($this->_params['charts'])) {
6a488035
TO
2671 $this->limit();
2672 }
2673 CRM_Utils_Hook::alterReportVar('sql', $this, $this);
2674
2675 $sql = "{$this->_select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy} {$this->_limit}";
02d451ab 2676 $this->addToDeveloperTab($sql);
6a488035
TO
2677 return $sql;
2678 }
2679
317a8023 2680 /**
2681 * Build group by clause.
2682 */
00be9182 2683 public function groupBy() {
4b885f84 2684 $this->storeGroupByArray();
6a488035 2685
70e504f2 2686 if (!empty($this->_groupByArray)) {
2687 $this->_groupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($this->_selectClauses, $this->_groupByArray);
6a488035
TO
2688 }
2689 }
2690
317a8023 2691 /**
2692 * Build order by clause.
2693 */
00be9182 2694 public function orderBy() {
9d72cede 2695 $this->_orderBy = "";
6a488035
TO
2696 $this->_sections = array();
2697 $this->storeOrderByArray();
9d72cede 2698 if (!empty($this->_orderByArray) && !$this->_rollup == 'WITH ROLLUP') {
6a488035
TO
2699 $this->_orderBy = "ORDER BY " . implode(', ', $this->_orderByArray);
2700 }
2701 $this->assign('sections', $this->_sections);
2702 }
f2947aea 2703
688d37c6 2704 /**
317a8023 2705 * Extract order by fields and store as an array.
2706 *
6a488035
TO
2707 * In some cases other functions want to know which fields are selected for ordering by
2708 * Separating this into a separate function allows it to be called separately from constructing
2709 * the order by clause
2710 */
00be9182 2711 public function storeOrderByArray() {
9d72cede 2712 $orderBys = array();
6a488035 2713
a7488080 2714 if (!empty($this->_params['order_bys']) &&
6a488035
TO
2715 is_array($this->_params['order_bys']) &&
2716 !empty($this->_params['order_bys'])
2717 ) {
2718
9907633b 2719 // Process order_bys in user-specified order
6a488035
TO
2720 foreach ($this->_params['order_bys'] as $orderBy) {
2721 $orderByField = array();
2722 foreach ($this->_columns as $tableName => $table) {
2723 if (array_key_exists('order_bys', $table)) {
2724 // For DAO columns defined in $this->_columns
2725 $fields = $table['order_bys'];
2726 }
2727 elseif (array_key_exists('extends', $table)) {
2728 // For custom fields referenced in $this->_customGroupExtends
1efec7ff 2729 $fields = CRM_Utils_Array::value('fields', $table, array());
6a488035 2730 }
6f993086
AH
2731 else {
2732 continue;
2733 }
6a488035
TO
2734 if (!empty($fields) && is_array($fields)) {
2735 foreach ($fields as $fieldName => $field) {
2736 if ($fieldName == $orderBy['column']) {
f2947aea 2737 $orderByField = array_merge($field, $orderBy);
6a488035
TO
2738 $orderByField['tplField'] = "{$tableName}_{$fieldName}";
2739 break 2;
2740 }
2741 }
2742 }
2743 }
2744
2745 if (!empty($orderByField)) {
55f71fa7 2746 $this->_orderByFields[$orderByField['tplField']] = $orderByField;
81a22d3d 2747 if ($this->groupConcatTested) {
2748 $orderBys[$orderByField['tplField']] = "{$orderByField['tplField']} {$orderBy['order']}";
2749 }
2750 else {
2751 // Not sure when this is preferable to using tplField (which has
2752 // definitely been tested to work in cases then this does not.
2753 // in caution not switching unless report has been tested for
2754 // group concat functionality.
2755 $orderBys[$orderByField['tplField']] = "{$orderByField['dbAlias']} {$orderBy['order']}";
2756 }
6a488035
TO
2757
2758 // Record any section headers for assignment to the template
a7488080 2759 if (!empty($orderBy['section'])) {
b5801e1d 2760 $orderByField['pageBreak'] = CRM_Utils_Array::value('pageBreak', $orderBy);
6a488035
TO
2761 $this->_sections[$orderByField['tplField']] = $orderByField;
2762 }
2763 }
2764 }
2765 }
2766
2767 $this->_orderByArray = $orderBys;
2768
2769 $this->assign('sections', $this->_sections);
2770 }
2771
81a22d3d 2772 /**
2773 * Determine unselected columns.
2774 *
2775 * @return array
2776 */
2777 public function unselectedOrderByColumns() {
2778 return array_diff_key($this->_orderByFields, $this->getSelectColumns());
2779 }
2780
74cf4551 2781 /**
317a8023 2782 * Determine unselected columns.
2783 *
74cf4551
EM
2784 * @return array
2785 */
00be9182 2786 public function unselectedSectionColumns() {
6a488035 2787 if (is_array($this->_sections)) {
55f71fa7 2788 return array_diff_key($this->_sections, $this->getSelectColumns());
6a488035
TO
2789 }
2790 else {
2791 return array();
2792 }
2793 }
2794
74cf4551 2795 /**
317a8023 2796 * Build output rows.
2797 *
2798 * @param string $sql
2799 * @param array $rows
74cf4551 2800 */
00be9182 2801 public function buildRows($sql, &$rows) {
6a488035
TO
2802 $dao = CRM_Core_DAO::executeQuery($sql);
2803 if (!is_array($rows)) {
2804 $rows = array();
2805 }
2806
2807 // use this method to modify $this->_columnHeaders
2808 $this->modifyColumnHeaders();
2809
2810 $unselectedSectionColumns = $this->unselectedSectionColumns();
2811
2812 while ($dao->fetch()) {
2813 $row = array();
2814 foreach ($this->_columnHeaders as $key => $value) {
2815 if (property_exists($dao, $key)) {
2816 $row[$key] = $dao->$key;
2817 }
2818 }
2819
2820 // section headers not selected for display need to be added to row
2821 foreach ($unselectedSectionColumns as $key => $values) {
2822 if (property_exists($dao, $key)) {
2823 $row[$key] = $dao->$key;
2824 }
2825 }
2826
2827 $rows[] = $row;
2828 }
2829 }
2830
2831 /**
317a8023 2832 * Calculate section totals.
2833 *
6a488035
TO
2834 * When "order by" fields are marked as sections, this assigns to the template
2835 * an array of total counts for each section. This data is used by the Smarty
317a8023 2836 * plugin {sectionTotal}.
6a488035 2837 */
00be9182 2838 public function sectionTotals() {
6a488035
TO
2839
2840 // Reports using order_bys with sections must populate $this->_selectAliases in select() method.
2841 if (empty($this->_selectAliases)) {
2842 return;
2843 }
2844
2845 if (!empty($this->_sections)) {
2846 // build the query with no LIMIT clause
2847 $select = str_ireplace('SELECT SQL_CALC_FOUND_ROWS ', 'SELECT ', $this->_select);
2848 $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy}";
2849
2850 // pull section aliases out of $this->_sections
2851 $sectionAliases = array_keys($this->_sections);
2852
2853 $ifnulls = array();
2854 foreach (array_merge($sectionAliases, $this->_selectAliases) as $alias) {
2855 $ifnulls[] = "ifnull($alias, '') as $alias";
2856 }
b708c08d 2857 $this->_select = "SELECT " . implode(", ", $ifnulls);
36d2f4d5 2858 $this->_select = CRM_Contact_BAO_Query::appendAnyValueToSelect($ifnulls, $sectionAliases);
6a488035 2859
971d41b1
CW
2860 // Group (un-limited) report by all aliases and get counts. This might
2861 // be done more efficiently when the contents of $sql are known, ie. by
2862 // overriding this method in the report class.
6a488035 2863
b708c08d 2864 $query = $this->_select .
9d72cede
EM
2865 ", count(*) as ct from ($sql) as subquery group by " .
2866 implode(", ", $sectionAliases);
6a488035
TO
2867
2868 // initialize array of total counts
2869 $totals = array();
2870 $dao = CRM_Core_DAO::executeQuery($query);
2871 while ($dao->fetch()) {
2872
2873 // let $this->_alterDisplay translate any integer ids to human-readable values.
2874 $rows[0] = $dao->toArray();
2875 $this->alterDisplay($rows);
2876 $row = $rows[0];
2877
2878 // add totals for all permutations of section values
9d72cede
EM
2879 $values = array();
2880 $i = 1;
6a488035
TO
2881 $aliasCount = count($sectionAliases);
2882 foreach ($sectionAliases as $alias) {
2883 $values[] = $row[$alias];
2884 $key = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values);
2885 if ($i == $aliasCount) {
2886 // the last alias is the lowest-level section header; use count as-is
2887 $totals[$key] = $dao->ct;
2888 }
2889 else {
2890 // other aliases are higher level; roll count into their total
2891 $totals[$key] += $dao->ct;
2892 }
2893 }
2894 }
2895 $this->assign('sectionTotals', $totals);
2896 }
2897 }
2898
317a8023 2899 /**
2900 * Modify column headers.
2901 */
00be9182 2902 public function modifyColumnHeaders() {
6a488035
TO
2903 // use this method to modify $this->_columnHeaders
2904 }
2905
06983500 2906 /**
2907 * Move totals columns to the right edge of the table.
2908 *
2909 * It seems like a more logical layout to have any totals columns on the far right regardless of
2910 * the location of the rest of their table.
2911 */
2912 public function moveSummaryColumnsToTheRightHandSide() {
2913 $statHeaders = (array_intersect_key($this->_columnHeaders, array_flip($this->_statFields)));
2914 $this->_columnHeaders = array_merge(array_diff_key($this->_columnHeaders, $statHeaders), $this->_columnHeaders, $statHeaders);
2915 }
2916
74cf4551 2917 /**
317a8023 2918 * Assign rows to the template.
2919 *
2920 * @param array $rows
74cf4551 2921 */
00be9182 2922 public function doTemplateAssignment(&$rows) {
6a488035
TO
2923 $this->assign_by_ref('columnHeaders', $this->_columnHeaders);
2924 $this->assign_by_ref('rows', $rows);
2925 $this->assign('statistics', $this->statistics($rows));
2926 }
2927
74cf4551 2928 /**
317a8023 2929 * Build report statistics.
2930 *
2931 * Override this method to build your own statistics.
2932 *
2933 * @param array $rows
74cf4551
EM
2934 *
2935 * @return array
2936 */
00be9182 2937 public function statistics(&$rows) {
6a488035
TO
2938 $statistics = array();
2939
2940 $count = count($rows);
8a2bee47 2941 // Why do we increment the count for rollup seems to artificially inflate the count.
c160fde8 2942 // It seems perhaps intentional to include the summary row in the count of results - although
2943 // this just seems odd.
6a488035
TO
2944 if ($this->_rollup && ($this->_rollup != '') && $this->_grandFlag) {
2945 $count++;
2946 }
2947
2948 $this->countStat($statistics, $count);
2949
2950 $this->groupByStat($statistics);
2951
2952 $this->filterStat($statistics);
2953
2954 return $statistics;
2955 }
2956
74cf4551 2957 /**
317a8023 2958 * Add count statistics.
2959 *
2960 * @param array $statistics
2961 * @param int $count
74cf4551 2962 */
00be9182 2963 public function countStat(&$statistics, $count) {
9d72cede
EM
2964 $statistics['counts']['rowCount'] = array(
2965 'title' => ts('Row(s) Listed'),
2966 'value' => $count,
6a488035
TO
2967 );
2968
2969 if ($this->_rowsFound && ($this->_rowsFound > $count)) {
9d72cede
EM
2970 $statistics['counts']['rowsFound'] = array(
2971 'title' => ts('Total Row(s)'),
2972 'value' => $this->_rowsFound,
6a488035
TO
2973 );
2974 }
2975 }
2976
74cf4551 2977 /**
317a8023 2978 * Add group by statistics.
2979 *
2980 * @param array $statistics
74cf4551 2981 */
00be9182 2982 public function groupByStat(&$statistics) {
a7488080 2983 if (!empty($this->_params['group_bys']) &&
6a488035
TO
2984 is_array($this->_params['group_bys']) &&
2985 !empty($this->_params['group_bys'])
2986 ) {
2987 foreach ($this->_columns as $tableName => $table) {
2988 if (array_key_exists('group_bys', $table)) {
2989 foreach ($table['group_bys'] as $fieldName => $field) {
a7488080 2990 if (!empty($this->_params['group_bys'][$fieldName])) {
6a488035
TO
2991 $combinations[] = $field['title'];
2992 }
2993 }
2994 }
2995 }
9d72cede
EM
2996 $statistics['groups'][] = array(
2997 'title' => ts('Grouping(s)'),
2998 'value' => implode(' & ', $combinations),
6a488035
TO
2999 );
3000 }
3001 }
3002
74cf4551 3003 /**
317a8023 3004 * Filter statistics.
3005 *
3006 * @param array $statistics
74cf4551 3007 */
00be9182 3008 public function filterStat(&$statistics) {
6a488035
TO
3009 foreach ($this->_columns as $tableName => $table) {
3010 if (array_key_exists('filters', $table)) {
3011 foreach ($table['filters'] as $fieldName => $field) {
5aae2a90 3012 if ((CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_DATE ||
3013 CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_TIME) &&
9d72cede
EM
3014 CRM_Utils_Array::value('operatorType', $field) !=
3015 CRM_Report_Form::OP_MONTH
3016 ) {
971d41b1
CW
3017 list($from, $to)
3018 = $this->getFromTo(
8f1445ea
DL
3019 CRM_Utils_Array::value("{$fieldName}_relative", $this->_params),
3020 CRM_Utils_Array::value("{$fieldName}_from", $this->_params),
3021 CRM_Utils_Array::value("{$fieldName}_to", $this->_params),
3022 CRM_Utils_Array::value("{$fieldName}_from_time", $this->_params),
3023 CRM_Utils_Array::value("{$fieldName}_to_time", $this->_params)
3024 );
0d8afee2 3025 $from_time_format = !empty($this->_params["{$fieldName}_from_time"]) ? 'h' : 'd';
9d72cede 3026 $from = CRM_Utils_Date::customFormat($from, NULL, array($from_time_format));
6a488035 3027
0d8afee2 3028 $to_time_format = !empty($this->_params["{$fieldName}_to_time"]) ? 'h' : 'd';
9d72cede 3029 $to = CRM_Utils_Date::customFormat($to, NULL, array($to_time_format));
6a488035
TO
3030
3031 if ($from || $to) {
3032 $statistics['filters'][] = array(
3033 'title' => $field['title'],
10a5be27 3034 'value' => ts("Between %1 and %2", array(1 => $from, 2 => $to)),
6a488035
TO
3035 );
3036 }
3037 elseif (in_array($rel = CRM_Utils_Array::value("{$fieldName}_relative", $this->_params),
9d72cede
EM
3038 array_keys($this->getOperationPair(CRM_Report_Form::OP_DATE))
3039 )) {
160d32e1 3040 $pair = $this->getOperationPair(CRM_Report_Form::OP_DATE);
6a488035
TO
3041 $statistics['filters'][] = array(
3042 'title' => $field['title'],
3043 'value' => $pair[$rel],
3044 );
3045 }
3046 }
3047 else {
3048 $op = CRM_Utils_Array::value("{$fieldName}_op", $this->_params);
3049 $value = NULL;
3050 if ($op) {
1b36206c 3051 $pair = $this->getOperationPair(
8f1445ea
DL
3052 CRM_Utils_Array::value('operatorType', $field),
3053 $fieldName
6a488035
TO
3054 );
3055 $min = CRM_Utils_Array::value("{$fieldName}_min", $this->_params);
3056 $max = CRM_Utils_Array::value("{$fieldName}_max", $this->_params);
3057 $val = CRM_Utils_Array::value("{$fieldName}_value", $this->_params);
2107cde9
CW
3058 if (in_array($op, array('bw', 'nbw')) && ($min || $max)) {
3059 $value = "{$pair[$op]} $min " . ts('and') . " $max";
3060 }
d3e86119 3061 elseif ($val && CRM_Utils_Array::value('operatorType', $field) & self::OP_ENTITYREF) {
2107cde9 3062 $this->setEntityRefDefaults($field, $tableName);
9d72cede
EM
3063 $result = civicrm_api3($field['attributes']['entity'], 'getlist',
3064 array('id' => $val) +
3065 CRM_Utils_Array::value('api', $field['attributes'], array()));
2107cde9
CW
3066 $values = array();
3067 foreach ($result['values'] as $v) {
3068 $values[] = $v['label'];
3069 }
3070 $value = "{$pair[$op]} " . implode(', ', $values);
6a488035
TO
3071 }
3072 elseif ($op == 'nll' || $op == 'nnll') {
3073 $value = $pair[$op];
3074 }
3075 elseif (is_array($val) && (!empty($val))) {
f974e915 3076 $options = CRM_Utils_Array::value('options', $field, array());
6a488035
TO
3077 foreach ($val as $key => $valIds) {
3078 if (isset($options[$valIds])) {
3079 $val[$key] = $options[$valIds];
3080 }
3081 }
9d72cede
EM
3082 $pair[$op] = (count($val) == 1) ? (($op == 'notin' || $op ==
3083 'mnot') ? ts('Is Not') : ts('Is')) : CRM_Utils_Array::value($op, $pair);
3084 $val = implode(', ', $val);
3085 $value = "{$pair[$op]} " . $val;
6a488035 3086 }
9d72cede
EM
3087 elseif (!is_array($val) && (!empty($val) || $val == '0') &&
3088 isset($field['options']) &&
6a488035
TO
3089 is_array($field['options']) && !empty($field['options'])
3090 ) {
9d72cede
EM
3091 $value = CRM_Utils_Array::value($op, $pair) . " " .
3092 CRM_Utils_Array::value($val, $field['options'], $val);
6a488035
TO
3093 }
3094 elseif ($val) {
3095 $value = CRM_Utils_Array::value($op, $pair) . " " . $val;
3096 }
3097 }
da7ac680 3098 if ($value && empty($field['no_display'])) {
9d72cede
EM
3099 $statistics['filters'][] = array(
3100 'title' => CRM_Utils_Array::value('title', $field),
3101 'value' => $value,
6a488035
TO
3102 );
3103 }
3104 }
3105 }
3106 }
3107 }
3108 }
3109
74cf4551 3110 /**
7d7c50f9
EM
3111 * End post processing.
3112 *
3113 * @param array|null $rows
74cf4551 3114 */
00be9182 3115 public function endPostProcess(&$rows = NULL) {
fae21877 3116 $this->assign('report_class', get_class($this));
9d72cede 3117 if ($this->_storeResultSet) {
ae555e90
DS
3118 $this->_resultSet = $rows;
3119 }
3120
6a488035
TO
3121 if ($this->_outputMode == 'print' ||
3122 $this->_outputMode == 'pdf' ||
3123 $this->_sendmail
3124 ) {
3125
3126 $content = $this->compileContent();
3127 $url = CRM_Utils_System::url("civicrm/report/instance/{$this->_id}",
9d72cede 3128 "reset=1", TRUE
6a488035
TO
3129 );
3130
3131 if ($this->_sendmail) {
3132 $config = CRM_Core_Config::singleton();
3133 $attachments = array();
3134
3135 if ($this->_outputMode == 'csv') {
971d41b1
CW
3136 $content
3137 = $this->_formValues['report_header'] . '<p>' . ts('Report URL') .
9d72cede
EM
3138 ": {$url}</p>" . '<p>' .
3139 ts('The report is attached as a CSV file.') . '</p>' .
3140 $this->_formValues['report_footer'];
3141
3142 $csvFullFilename = $config->templateCompileDir .
3143 CRM_Utils_File::makeFileName('CiviReport.csv');
6a488035
TO
3144 $csvContent = CRM_Report_Utils_Report::makeCsv($this, $rows);
3145 file_put_contents($csvFullFilename, $csvContent);
3146 $attachments[] = array(
3147 'fullPath' => $csvFullFilename,
3148 'mime_type' => 'text/csv',
3149 'cleanName' => 'CiviReport.csv',
3150 );
3151 }
3152 if ($this->_outputMode == 'pdf') {
3153 // generate PDF content
9d72cede
EM
3154 $pdfFullFilename = $config->templateCompileDir .
3155 CRM_Utils_File::makeFileName('CiviReport.pdf');
6a488035
TO
3156 file_put_contents($pdfFullFilename,
3157 CRM_Utils_PDF_Utils::html2pdf($content, "CiviReport.pdf",
3158 TRUE, array('orientation' => 'landscape')
3159 )
3160 );
3161 // generate Email Content
971d41b1
CW
3162 $content
3163 = $this->_formValues['report_header'] . '<p>' . ts('Report URL') .
9d72cede
EM
3164 ": {$url}</p>" . '<p>' .
3165 ts('The report is attached as a PDF file.') . '</p>' .
3166 $this->_formValues['report_footer'];
6a488035
TO
3167
3168 $attachments[] = array(
3169 'fullPath' => $pdfFullFilename,
3170 'mime_type' => 'application/pdf',
3171 'cleanName' => 'CiviReport.pdf',
3172 );
3173 }
3174
3175 if (CRM_Report_Utils_Report::mailReport($content, $this->_id,
9d72cede
EM
3176 $this->_outputMode, $attachments
3177 )
3178 ) {
6a488035
TO
3179 CRM_Core_Session::setStatus(ts("Report mail has been sent."), ts('Sent'), 'success');
3180 }
3181 else {
3182 CRM_Core_Session::setStatus(ts("Report mail could not be sent."), ts('Mail Error'), 'error');
3183 }
73b448bf 3184 return;
6a488035
TO
3185 }
3186 elseif ($this->_outputMode == 'print') {
3187 echo $content;
3188 }
3189 else {
3190 if ($chartType = CRM_Utils_Array::value('charts', $this->_params)) {
3191 $config = CRM_Core_Config::singleton();
3192 //get chart image name
3193 $chartImg = $this->_chartId . '.png';
3194 //get image url path
971d41b1
CW
3195 $uploadUrl
3196 = str_replace('/persist/contribute/', '/persist/', $config->imageUploadURL) .
9d72cede 3197 'openFlashChart/';
6a488035
TO
3198 $uploadUrl .= $chartImg;
3199 //get image doc path to overwrite
971d41b1
CW
3200 $uploadImg
3201 = str_replace('/persist/contribute/', '/persist/', $config->imageUploadDir) .
9d72cede 3202 'openFlashChart/' . $chartImg;
6a488035
TO
3203 //Load the image
3204 $chart = imagecreatefrompng($uploadUrl);
2fe3c48d 3205 //convert it into formatted png
d42a224c 3206 CRM_Utils_System::setHttpHeader('Content-type', 'image/png');
6a488035
TO
3207 //overwrite with same image
3208 imagepng($chart, $uploadImg);
3209 //delete the object
3210 imagedestroy($chart);
3211 }
3212 CRM_Utils_PDF_Utils::html2pdf($content, "CiviReport.pdf", FALSE, array('orientation' => 'landscape'));
3213 }
3214 CRM_Utils_System::civiExit();
3215 }
3216 elseif ($this->_outputMode == 'csv') {
3217 CRM_Report_Utils_Report::export2csv($this, $rows);
3218 }
3219 elseif ($this->_outputMode == 'group') {
3220 $group = $this->_params['groups'];
3221 $this->add2group($group);
3222 }
6a488035 3223 }
8f1445ea 3224
8a925f33 3225 /**
7d7c50f9
EM
3226 * Set store result set indicator to TRUE.
3227 *
8a925f33
EM
3228 * @todo explain what this does
3229 */
00be9182 3230 public function storeResultSet() {
ae555e90
DS
3231 $this->_storeResultSet = TRUE;
3232 }
3233
74cf4551 3234 /**
7d7c50f9
EM
3235 * Get result set.
3236 *
74cf4551
EM
3237 * @return bool
3238 */
00be9182 3239 public function getResultSet() {
ae555e90
DS
3240 return $this->_resultSet;
3241 }
3242
55f71fa7 3243 /**
3244 * Get the sql used to generate the report.
3245 *
3246 * @return string
3247 */
3248 public function getReportSql() {
3249 return $this->sqlArray;
3250 }
3251
74cf4551 3252 /**
fe482240 3253 * Use the form name to create the tpl file name.
74cf4551
EM
3254 *
3255 * @return string
74cf4551 3256 */
00be9182 3257 public function getTemplateFileName() {
6a488035 3258 $defaultTpl = parent::getTemplateFileName();
9d72cede 3259 $template = CRM_Core_Smarty::singleton();
6a488035
TO
3260 if (!$template->template_exists($defaultTpl)) {
3261 $defaultTpl = 'CRM/Report/Form.tpl';
3262 }
3263 return $defaultTpl;
3264 }
3265
688d37c6 3266 /**
fe482240 3267 * Compile the report content.
317a8023 3268 *
688d37c6 3269 * Although this function is super-short it is useful to keep separate so it can be over-ridden by report classes.
6a488035 3270 *
74cf4551
EM
3271 * @return string
3272 */
00be9182 3273 public function compileContent() {
8aac22c8 3274 $templateFile = $this->getHookedTemplateFileName();
1a7356e7 3275 return CRM_Utils_Array::value('report_header', $this->_formValues) .
9d72cede 3276 CRM_Core_Form::$_template->fetch($templateFile) .
1a7356e7 3277 CRM_Utils_Array::value('report_footer', $this->_formValues);
6a488035
TO
3278 }
3279
3280
317a8023 3281 /**
3282 * Post process function.
3283 */
00be9182 3284 public function postProcess() {
6a488035
TO
3285 // get ready with post process params
3286 $this->beginPostProcess();
3287
3288 // build query
3289 $sql = $this->buildQuery();
3290
3291 // build array of result based on column headers. This method also allows
3292 // modifying column headers before using it to build result set i.e $rows.
3293 $rows = array();
3294 $this->buildRows($sql, $rows);
3295
3296 // format result set.
3297 $this->formatDisplay($rows);
3298
3299 // assign variables to templates
3300 $this->doTemplateAssignment($rows);
3301
3302 // do print / pdf / instance stuff if needed
3303 $this->endPostProcess($rows);
3304 }
3305
74cf4551 3306 /**
317a8023 3307 * Set limit.
3308 *
74cf4551 3309 * @param int $rowCount
317a8023 3310 *
688d37c6 3311 * @return array
74cf4551 3312 */
00be9182 3313 public function limit($rowCount = self::ROW_COUNT_LIMIT) {
6a488035
TO
3314 // lets do the pager if in html mode
3315 $this->_limit = NULL;
77b97be7 3316
dbb4a0f9
PN
3317 // CRM-14115, over-ride row count if rowCount is specified in URL
3318 if ($this->_dashBoardRowCount) {
3319 $rowCount = $this->_dashBoardRowCount;
3320 }
182f5081 3321 if ($this->addPaging) {
f0b5b73e 3322 $this->_select = preg_replace('/SELECT(\s+SQL_CALC_FOUND_ROWS)?\s+/i', 'SELECT SQL_CALC_FOUND_ROWS ', $this->_select);
6a488035 3323
a3d827a7 3324 $pageId = CRM_Utils_Request::retrieve('crmPID', 'Integer');
6a488035 3325
8a925f33
EM
3326 // @todo all http vars should be extracted in the preProcess
3327 // - not randomly in the class
6a488035
TO
3328 if (!$pageId && !empty($_POST)) {
3329 if (isset($_POST['PagerBottomButton']) && isset($_POST['crmPID_B'])) {
8a925f33 3330 $pageId = max((int) $_POST['crmPID_B'], 1);
6a488035
TO
3331 }
3332 elseif (isset($_POST['PagerTopButton']) && isset($_POST['crmPID'])) {
8a925f33 3333 $pageId = max((int) $_POST['crmPID'], 1);
6a488035
TO
3334 }
3335 unset($_POST['crmPID_B'], $_POST['crmPID']);
3336 }
3337
3338 $pageId = $pageId ? $pageId : 1;
3339 $this->set(CRM_Utils_Pager::PAGE_ID, $pageId);
3340 $offset = ($pageId - 1) * $rowCount;
3341
bf00d1b6
DL
3342 $offset = CRM_Utils_Type::escape($offset, 'Int');
3343 $rowCount = CRM_Utils_Type::escape($rowCount, 'Int');
3344
dd3a4117 3345 $this->_limit = " LIMIT $offset, $rowCount";
6a488035
TO
3346 return array($offset, $rowCount);
3347 }
9d72cede
EM
3348 if ($this->_limitValue) {
3349 if ($this->_offsetValue) {
6f900755
E
3350 $this->_limit = " LIMIT {$this->_offsetValue}, {$this->_limitValue} ";
3351 }
3352 else {
3353 $this->_limit = " LIMIT " . $this->_limitValue;
3354 }
3355 }
6a488035
TO
3356 }
3357
74cf4551 3358 /**
317a8023 3359 * Set pager.
3360 *
74cf4551
EM
3361 * @param int $rowCount
3362 */
00be9182 3363 public function setPager($rowCount = self::ROW_COUNT_LIMIT) {
dbb4a0f9
PN
3364 // CRM-14115, over-ride row count if rowCount is specified in URL
3365 if ($this->_dashBoardRowCount) {
3366 $rowCount = $this->_dashBoardRowCount;
3367 }
3368
6a488035 3369 if ($this->_limit && ($this->_limit != '')) {
c160fde8 3370 if (!$this->_rowsFound) {
3371 $sql = "SELECT FOUND_ROWS();";
3372 $this->_rowsFound = CRM_Core_DAO::singleValueQuery($sql);
3373 }
9d72cede 3374 $params = array(
6a488035
TO
3375 'total' => $this->_rowsFound,
3376 'rowCount' => $rowCount,
3377 'status' => ts('Records') . ' %%StatusMessage%%',
3378 'buttonBottom' => 'PagerBottomButton',
3379 'buttonTop' => 'PagerTopButton',
6a488035 3380 );
f45db615 3381 if (!empty($this->controller)) {
3382 // This happens when being called from the api Really we want the api to be able to
3383 // pass paging parameters, but at this stage just preventing test crashes.
3384 $params['pageID'] = $this->get(CRM_Utils_Pager::PAGE_ID);
3385 }
6a488035
TO
3386
3387 $pager = new CRM_Utils_Pager($params);
3388 $this->assign_by_ref('pager', $pager);
ecc20f0e 3389 $this->ajaxResponse['totalRows'] = $this->_rowsFound;
6a488035
TO
3390 }
3391 }
3392
74cf4551 3393 /**
43c1fa19 3394 * Build a group filter with contempt for large data sets.
3395 *
3396 * This function has been retained as it takes time to migrate the reports over
3397 * to the new method which will not crash on large datasets.
3398 *
3399 * @deprecated
317a8023 3400 *
3401 * @param string $field
3402 * @param mixed $value
3403 * @param string $op
74cf4551
EM
3404 *
3405 * @return string
3406 */
43c1fa19 3407 public function legacySlowGroupFilterClause($field, $value, $op) {
6a488035
TO
3408 $smartGroupQuery = "";
3409
3410 $group = new CRM_Contact_DAO_Group();
3411 $group->is_active = 1;
3412 $group->find();
3413 $smartGroups = array();
3414 while ($group->fetch()) {
eae0f0d9 3415 if (in_array($group->id, (array) $this->_params['gid_value']) &&
9d72cede
EM
3416 $group->saved_search_id
3417 ) {
6a488035
TO
3418 $smartGroups[] = $group->id;
3419 }
3420 }
3421
3422 CRM_Contact_BAO_GroupContactCache::check($smartGroups);
3423
3424 $smartGroupQuery = '';
3425 if (!empty($smartGroups)) {
3426 $smartGroups = implode(',', $smartGroups);
3427 $smartGroupQuery = " UNION DISTINCT
3428 SELECT DISTINCT smartgroup_contact.contact_id
3429 FROM civicrm_group_contact_cache smartgroup_contact
3430 WHERE smartgroup_contact.group_id IN ({$smartGroups}) ";
3431 }
3432
29fc2b79 3433 $sqlOp = $this->getSQLOperator($op);
6a488035
TO
3434 if (!is_array($value)) {
3435 $value = array($value);
3436 }
8e4785fa 3437 //include child groups if any
3438 $value = array_merge($value, CRM_Contact_BAO_Group::getChildGroupIds($value));
3439
6a488035
TO
3440 $clause = "{$field['dbAlias']} IN (" . implode(', ', $value) . ")";
3441
f3e8e853 3442 $contactAlias = $this->_aliases['civicrm_contact'];
3443 if (!empty($this->relationType) && $this->relationType == 'b_a') {
3444 $contactAlias = $this->_aliases['civicrm_contact_b'];
3445 }
3446 return " {$contactAlias}.id {$sqlOp} (
6a488035
TO
3447 SELECT DISTINCT {$this->_aliases['civicrm_group']}.contact_id
3448 FROM civicrm_group_contact {$this->_aliases['civicrm_group']}
3449 WHERE {$clause} AND {$this->_aliases['civicrm_group']}.status = 'Added'
3450 {$smartGroupQuery} ) ";
3451 }
3452
43c1fa19 3453 /**
3454 * Build where clause for groups.
3455 *
3456 * @param string $field
3457 * @param mixed $value
3458 * @param string $op
3459 *
3460 * @return string
3461 */
3462 public function whereGroupClause($field, $value, $op) {
3463 if ($this->groupFilterNotOptimised) {
3464 return $this->legacySlowGroupFilterClause($field, $value, $op);
3465 }
3466 if ($op === 'notin') {
3467 return " group_temp_table.id IS NULL ";
3468 }
3469 // We will have used an inner join instead.
3470 return "1";
3471 }
3472
3473
3474 /**
3475 * Create a table of the contact ids included by the group filter.
3476 *
3477 * This function is called by both the api (tests) and the UI.
3478 */
3479 public function buildGroupTempTable() {
a2e4e741 3480 if (!empty($this->groupTempTable) || empty($this->_params['gid_value']) || $this->groupFilterNotOptimised) {
43c1fa19 3481 return;
3482 }
3483 $filteredGroups = (array) $this->_params['gid_value'];
3484
3485 $groups = civicrm_api3('Group', 'get', array(
3486 'is_active' => 1,
3487 'id' => array('IN' => $filteredGroups),
3488 'saved_search_id' => array('>' => 0),
3489 'return' => 'id',
3490 ));
3491 $smartGroups = array_keys($groups['values']);
3492
3493 $query = "
b6963180 3494 SELECT DISTINCT group_contact.contact_id as id
43c1fa19 3495 FROM civicrm_group_contact group_contact
3496 WHERE group_contact.group_id IN (" . implode(', ', $filteredGroups) . ")
3497 AND group_contact.status = 'Added' ";
3498
3499 if (!empty($smartGroups)) {
3500 CRM_Contact_BAO_GroupContactCache::check($smartGroups);
3501 $smartGroups = implode(',', $smartGroups);
3502 $query .= "
3503 UNION DISTINCT
3504 SELECT smartgroup_contact.contact_id as id
3505 FROM civicrm_group_contact_cache smartgroup_contact
3506 WHERE smartgroup_contact.group_id IN ({$smartGroups}) ";
3507 }
3508
3509 $this->groupTempTable = 'civicrm_report_temp_group_' . date('Ymd_') . uniqid();
3510 $this->executeReportQuery("
a2e4e741 3511 CREATE TEMPORARY TABLE $this->groupTempTable $this->_databaseAttributes
43c1fa19 3512 $query
3513 ");
3514 CRM_Core_DAO::executeQuery("ALTER TABLE $this->groupTempTable ADD INDEX i_id(id)");
3515 }
3516
3517 /**
3518 * Execute query and add it to the developer tab.
3519 *
3520 * @param string $query
3521 * @param array $params
3522 *
3523 * @return \CRM_Core_DAO|object
3524 */
3525 protected function executeReportQuery($query, $params = array()) {
3526 $this->addToDeveloperTab($query);
3527 return CRM_Core_DAO::executeQuery($query, $params);
3528 }
3529
74cf4551 3530 /**
317a8023 3531 * Build where clause for tags.
3532 *
3533 * @param string $field
3534 * @param mixed $value
3535 * @param string $op
74cf4551
EM
3536 *
3537 * @return string
3538 */
00be9182 3539 public function whereTagClause($field, $value, $op) {
6a488035
TO
3540 // not using left join in query because if any contact
3541 // belongs to more than one tag, results duplicate
3542 // entries.
29fc2b79 3543 $sqlOp = $this->getSQLOperator($op);
6a488035
TO
3544 if (!is_array($value)) {
3545 $value = array($value);
3546 }
3547 $clause = "{$field['dbAlias']} IN (" . implode(', ', $value) . ")";
ed795723
JM
3548 $entity_table = $this->_tagFilterTable;
3549 return " {$this->_aliases[$entity_table]}.id {$sqlOp} (
6a488035
TO
3550 SELECT DISTINCT {$this->_aliases['civicrm_tag']}.entity_id
3551 FROM civicrm_entity_tag {$this->_aliases['civicrm_tag']}
ed795723 3552 WHERE entity_table = '$entity_table' AND {$clause} ) ";
6a488035
TO
3553 }
3554
f587aa17
EM
3555 /**
3556 * Generate membership organization clause.
3557 *
3558 * @param mixed $value
3559 * @param string $op SQL Operator
3560 *
3561 * @return string
3562 */
3563 public function whereMembershipOrgClause($value, $op) {
114a2c85
DG
3564 $sqlOp = $this->getSQLOperator($op);
3565 if (!is_array($value)) {
3566 $value = array($value);
3567 }
258e2add 3568
114a2c85
DG
3569 $tmp_membership_org_sql_list = implode(', ', $value);
3570 return " {$this->_aliases['civicrm_contact']}.id {$sqlOp} (
97709b72 3571 SELECT DISTINCT mem.contact_id
258e2add 3572 FROM civicrm_membership mem
97709b72 3573 LEFT JOIN civicrm_membership_status mem_status ON mem.status_id = mem_status.id
258e2add 3574 LEFT JOIN civicrm_membership_type mt ON mem.membership_type_id = mt.id
9d72cede
EM
3575 WHERE mt.member_of_contact_id IN (" .
3576 $tmp_membership_org_sql_list . ")
97709b72
DG
3577 AND mt.is_active = '1'
3578 AND mem_status.is_current_member = '1'
3579 AND mem_status.is_active = '1' ) ";
9d72cede 3580 }
114a2c85 3581
f587aa17 3582 /**
fe482240 3583 * Generate Membership Type SQL Clause.
317a8023 3584 *
f587aa17
EM
3585 * @param mixed $value
3586 * @param string $op
3587 *
3588 * @return string
3589 * SQL query string
3590 */
3591 public function whereMembershipTypeClause($value, $op) {
114a2c85
DG
3592 $sqlOp = $this->getSQLOperator($op);
3593 if (!is_array($value)) {
3594 $value = array($value);
3595 }
258e2add 3596
9d72cede 3597 $tmp_membership_sql_list = implode(', ', $value);
114a2c85 3598 return " {$this->_aliases['civicrm_contact']}.id {$sqlOp} (
97709b72 3599 SELECT DISTINCT mem.contact_id
258e2add 3600 FROM civicrm_membership mem
3601 LEFT JOIN civicrm_membership_status mem_status ON mem.status_id = mem_status.id
3602 LEFT JOIN civicrm_membership_type mt ON mem.membership_type_id = mt.id
9d72cede
EM
3603 WHERE mem.membership_type_id IN (" .
3604 $tmp_membership_sql_list . ")
97709b72
DG
3605 AND mt.is_active = '1'
3606 AND mem_status.is_current_member = '1'
3607 AND mem_status.is_active = '1' ) ";
114a2c85 3608 }
258e2add 3609
74cf4551 3610 /**
f0384ec0
CW
3611 * Buld contact acl clause
3612 * @deprecated in favor of buildPermissionClause
317a8023 3613 *
74cf4551
EM
3614 * @param string $tableAlias
3615 */
00be9182 3616 public function buildACLClause($tableAlias = 'contact_a') {
6a488035
TO
3617 list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias);
3618 }
3619
f0384ec0
CW
3620 /**
3621 * Build the permision clause for all entities in this report
3622 */
3623 public function buildPermissionClause() {
3624 $ret = array();
3625 foreach ($this->selectedTables() as $tableName) {
3626 $baoName = str_replace('_DAO_', '_BAO_', CRM_Core_DAO_AllCoreTables::getClassForTable($tableName));
3627 if ($baoName && class_exists($baoName) && !empty($this->_columns[$tableName]['alias'])) {
3628 $tableAlias = $this->_columns[$tableName]['alias'];
3629 $clauses = array_filter($baoName::getSelectWhereClause($tableAlias));
3630 foreach ($clauses as $field => $clause) {
3631 // Skip contact_id field if redundant
3632 if ($field != 'contact_id' || !in_array('civicrm_contact', $this->selectedTables())) {
3633 $ret["$tableName.$field"] = $clause;
3634 }
3635 }
3636 }
3637 }
3638 // Override output from buildACLClause
3639 $this->_aclFrom = NULL;
3640 $this->_aclWhere = implode(' AND ', $ret);
3641 }
3642
74cf4551 3643 /**
317a8023 3644 * Add custom data to the columns.
3645 *
74cf4551
EM
3646 * @param bool $addFields
3647 * @param array $permCustomGroupIds
3648 */
00be9182 3649 public function addCustomDataToColumns($addFields = TRUE, $permCustomGroupIds = array()) {
6a488035
TO
3650 if (empty($this->_customGroupExtends)) {
3651 return;
3652 }
3653 if (!is_array($this->_customGroupExtends)) {
3654 $this->_customGroupExtends = array($this->_customGroupExtends);
3655 }
3656 $customGroupWhere = '';
3657 if (!empty($permCustomGroupIds)) {
9d72cede
EM
3658 $customGroupWhere = "cg.id IN (" . implode(',', $permCustomGroupIds) .
3659 ") AND";
6a488035
TO
3660 }
3661 $sql = "
3662SELECT cg.table_name, cg.title, cg.extends, cf.id as cf_id, cf.label,
3663 cf.column_name, cf.data_type, cf.html_type, cf.option_group_id, cf.time_format
3664FROM civicrm_custom_group cg
3665INNER JOIN civicrm_custom_field cf ON cg.id = cf.custom_group_id
3666WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
3667 {$customGroupWhere}
3668 cg.is_active = 1 AND
3669 cf.is_active = 1 AND
3670 cf.is_searchable = 1
3671ORDER BY cg.weight, cf.weight";
3672 $customDAO = CRM_Core_DAO::executeQuery($sql);
3673
3674 $curTable = NULL;
3675 while ($customDAO->fetch()) {
3676 if ($customDAO->table_name != $curTable) {
3677 $curTable = $customDAO->table_name;
3678 $curFields = $curFilters = array();
3679
3680 // dummy dao object
3681 $this->_columns[$curTable]['dao'] = 'CRM_Contact_DAO_Contact';
3682 $this->_columns[$curTable]['extends'] = $customDAO->extends;
3683 $this->_columns[$curTable]['grouping'] = $customDAO->table_name;
3684 $this->_columns[$curTable]['group_title'] = $customDAO->title;
3685
106e51b4 3686 foreach (array('fields', 'filters', 'group_bys') as $colKey) {
6a488035
TO
3687 if (!array_key_exists($colKey, $this->_columns[$curTable])) {
3688 $this->_columns[$curTable][$colKey] = array();
3689 }
3690 }
3691 }
3692 $fieldName = 'custom_' . $customDAO->cf_id;
3693
3694 if ($addFields) {
3695 // this makes aliasing work in favor
3696 $curFields[$fieldName] = array(
3697 'name' => $customDAO->column_name,
3698 'title' => $customDAO->label,
3699 'dataType' => $customDAO->data_type,
3700 'htmlType' => $customDAO->html_type,
3701 );
3702 }
3703 if ($this->_customGroupFilters) {
3704 // this makes aliasing work in favor
3705 $curFilters[$fieldName] = array(
3706 'name' => $customDAO->column_name,
3707 'title' => $customDAO->label,
3708 'dataType' => $customDAO->data_type,
3709 'htmlType' => $customDAO->html_type,
3710 );
3711 }
3712
3713 switch ($customDAO->data_type) {
3714 case 'Date':
3715 // filters
3716 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_DATE;
3717 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_DATE;
3718 // CRM-6946, show time part for datetime date fields
3719 if ($customDAO->time_format) {
3720 $curFields[$fieldName]['type'] = CRM_Utils_Type::T_TIMESTAMP;
3721 }
3722 break;
3723
3724 case 'Boolean':
3725 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_SELECT;
ccc29f8e 3726 $curFilters[$fieldName]['options'] = array('' => ts('- select -')) + CRM_Core_PseudoConstant::get('CRM_Core_BAO_CustomField', 'custom_' . $customDAO->cf_id, array(), 'search');
6a488035
TO
3727 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_INT;
3728 break;
3729
3730 case 'Int':
3731 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_INT;
3732 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_INT;
3733 break;
3734
3735 case 'Money':
3736 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_FLOAT;
3737 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_MONEY;
c160fde8 3738 $curFields[$fieldName]['type'] = CRM_Utils_Type::T_MONEY;
6a488035
TO
3739 break;
3740
3741 case 'Float':
3742 $curFilters[$fieldName]['operatorType'] = CRM_Report_Form::OP_FLOAT;
3743 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_FLOAT;
3744 break;
3745
3746 case 'String':
6a488035 3747 case 'StateProvince':
6a488035 3748 case 'Country':
106e51b4
CW
3749 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3750
3751 $options = CRM_Core_PseudoConstant::get('CRM_Core_BAO_CustomField', 'custom_' . $customDAO->cf_id, array(), 'search');
3752 if ($options !== FALSE) {
3753 $curFilters[$fieldName]['operatorType'] = CRM_Core_BAO_CustomField::isSerialized($customDAO) ? CRM_Report_Form::OP_MULTISELECT_SEPARATOR : CRM_Report_Form::OP_MULTISELECT;
3754 $curFilters[$fieldName]['options'] = $options;
6a488035 3755 }
6a488035
TO
3756 break;
3757
3758 case 'ContactReference':
3759 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3760 $curFilters[$fieldName]['name'] = 'display_name';
3761 $curFilters[$fieldName]['alias'] = "contact_{$fieldName}_civireport";
3762
3763 $curFields[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3764 $curFields[$fieldName]['name'] = 'display_name';
3765 $curFields[$fieldName]['alias'] = "contact_{$fieldName}_civireport";
3766 break;
3767
3768 default:
3769 $curFields[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3770 $curFilters[$fieldName]['type'] = CRM_Utils_Type::T_STRING;
3771 }
3772
d79e67a6 3773 // CRM-19401 fix
3774 if ($customDAO->html_type == 'Select' && !array_key_exists('options', $curFilters[$fieldName])) {
3775 $options = CRM_Core_PseudoConstant::get('CRM_Core_BAO_CustomField', 'custom_' . $customDAO->cf_id, array(), 'search');
3776 if ($options !== FALSE) {
3777 $curFilters[$fieldName]['operatorType'] = CRM_Core_BAO_CustomField::isSerialized($customDAO) ? CRM_Report_Form::OP_MULTISELECT_SEPARATOR : CRM_Report_Form::OP_MULTISELECT;
3778 $curFilters[$fieldName]['options'] = $options;
3779 }
3780 }
3781
6a488035 3782 if (!array_key_exists('type', $curFields[$fieldName])) {
fc161185 3783 $curFields[$fieldName]['type'] = CRM_Utils_Array::value('type', $curFilters[$fieldName], array());
6a488035
TO
3784 }
3785
3786 if ($addFields) {
3787 $this->_columns[$curTable]['fields'] = array_merge($this->_columns[$curTable]['fields'], $curFields);
3788 }
3789 if ($this->_customGroupFilters) {
3790 $this->_columns[$curTable]['filters'] = array_merge($this->_columns[$curTable]['filters'], $curFilters);
3791 }
3792 if ($this->_customGroupGroupBy) {
3793 $this->_columns[$curTable]['group_bys'] = array_merge($this->_columns[$curTable]['group_bys'], $curFields);
3794 }
3795 }
3796 }
3797
317a8023 3798 /**
3799 * Build custom data from clause.
568a0946 3800 *
3801 * @param bool $joinsForFiltersOnly
3802 * Only include joins to support filters. This would be used if creating a table of contacts to include first.
317a8023 3803 */
568a0946 3804 public function customDataFrom($joinsForFiltersOnly = FALSE) {
6a488035
TO
3805 if (empty($this->_customGroupExtends)) {
3806 return;
3807 }
3808 $mapper = CRM_Core_BAO_CustomQuery::$extendsMap;
fae6de7c
JL
3809 //CRM-18276 GROUP_CONCAT could be used with singleValueQuery and then exploded,
3810 //but by default that truncates to 1024 characters, which causes errors with installs with lots of custom field sets
ae783a02 3811 $customTables = array();
33621c4f 3812 $customTablesDAO = CRM_Core_DAO::executeQuery("SELECT table_name FROM civicrm_custom_group");
fae6de7c
JL
3813 while ($customTablesDAO->fetch()) {
3814 $customTables[] = $customTablesDAO->table_name;
3815 }
6a488035
TO
3816
3817 foreach ($this->_columns as $table => $prop) {
55f71fa7 3818 if (in_array($table, $customTables)) {
6a488035 3819 $extendsTable = $mapper[$prop['extends']];
568a0946 3820 // Check field is required for rendering the report.
3821 if ((!$this->isFieldSelected($prop)) || ($joinsForFiltersOnly && !$this->isFieldFiltered($prop))) {
6a488035
TO
3822 continue;
3823 }
4c9d78ea 3824 $baseJoin = CRM_Utils_Array::value($prop['extends'], $this->_customGroupExtendsJoin, "{$this->_aliases[$extendsTable]}.id");
6a488035 3825
9d72cede 3826 $customJoin = is_array($this->_customGroupJoin) ? $this->_customGroupJoin[$table] : $this->_customGroupJoin;
6a488035 3827 $this->_from .= "
aa1aa08e 3828{$customJoin} {$table} {$this->_aliases[$table]} ON {$this->_aliases[$table]}.entity_id = {$baseJoin}";
6a488035
TO
3829 // handle for ContactReference
3830 if (array_key_exists('fields', $prop)) {
3831 foreach ($prop['fields'] as $fieldName => $field) {
9d72cede
EM
3832 if (CRM_Utils_Array::value('dataType', $field) ==
3833 'ContactReference'
3834 ) {
6a488035
TO
3835 $columnName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', CRM_Core_BAO_CustomField::getKeyID($fieldName), 'column_name');
3836 $this->_from .= "
3837LEFT JOIN civicrm_contact {$field['alias']} ON {$field['alias']}.id = {$this->_aliases[$table]}.{$columnName} ";
3838 }
3839 }
3840 }
3841 }
3842 }
3843 }
3844
74cf4551 3845 /**
317a8023 3846 * Check if the field is selected.
3847 *
3848 * @param string $prop
74cf4551
EM
3849 *
3850 * @return bool
3851 */
00be9182 3852 public function isFieldSelected($prop) {
6a488035
TO
3853 if (empty($prop)) {
3854 return FALSE;
3855 }
3856
3857 if (!empty($this->_params['fields'])) {
3858 foreach (array_keys($prop['fields']) as $fieldAlias) {
3859 $customFieldId = CRM_Core_BAO_CustomField::getKeyID($fieldAlias);
3860 if ($customFieldId) {
3861 if (array_key_exists($fieldAlias, $this->_params['fields'])) {
3862 return TRUE;
3863 }
3864
3865 //might be survey response field.
9d72cede
EM
3866 if (!empty($this->_params['fields']['survey_response']) &&
3867 !empty($prop['fields'][$fieldAlias]['isSurveyResponseField'])
3868 ) {
6a488035
TO
3869 return TRUE;
3870 }
3871 }
3872 }
3873 }
3874
3875 if (!empty($this->_params['group_bys']) && $this->_customGroupGroupBy) {
3876 foreach (array_keys($prop['group_bys']) as $fieldAlias) {
9d72cede
EM
3877 if (array_key_exists($fieldAlias, $this->_params['group_bys']) &&
3878 CRM_Core_BAO_CustomField::getKeyID($fieldAlias)
3879 ) {
6a488035
TO
3880 return TRUE;
3881 }
3882 }
3883 }
3884
3885 if (!empty($this->_params['order_bys'])) {
3886 foreach (array_keys($prop['fields']) as $fieldAlias) {
3887 foreach ($this->_params['order_bys'] as $orderBy) {
9d72cede
EM
3888 if ($fieldAlias == $orderBy['column'] &&
3889 CRM_Core_BAO_CustomField::getKeyID($fieldAlias)
3890 ) {
6a488035
TO
3891 return TRUE;
3892 }
3893 }
3894 }
3895 }
568a0946 3896 return $this->isFieldFiltered($prop);
3897
3898 }
6a488035 3899
568a0946 3900 /**
3901 * Check if the field is used as a filter.
3902 *
3903 * @param string $prop
3904 *
3905 * @return bool
3906 */
3907 protected function isFieldFiltered($prop) {
6a488035
TO
3908 if (!empty($prop['filters']) && $this->_customGroupFilters) {
3909 foreach ($prop['filters'] as $fieldAlias => $val) {
3910 foreach (array(
9d72cede
EM
3911 'value',
3912 'min',
3913 'max',
3914 'relative',
3915 'from',
21dfd5f5 3916 'to',
9d72cede 3917 ) as $attach) {
6a488035 3918 if (isset($this->_params[$fieldAlias . '_' . $attach]) &&
dfe4b2f5 3919 (!empty($this->_params[$fieldAlias . '_' . $attach])
9d72cede
EM
3920 || ($attach != 'relative' &&
3921 $this->_params[$fieldAlias . '_' . $attach] == '0')
dfe4b2f5 3922 )
9d72cede 3923 ) {
6a488035
TO
3924 return TRUE;
3925 }
3926 }
a7488080 3927 if (!empty($this->_params[$fieldAlias . '_op']) &&
6a488035
TO
3928 in_array($this->_params[$fieldAlias . '_op'], array('nll', 'nnll'))
3929 ) {
3930 return TRUE;
3931 }
3932 }
3933 }
3934
3935 return FALSE;
3936 }
3937
3938 /**
31a8b5f0 3939 * Check for empty order_by configurations and remove them.
3940 *
3941 * Also set template to hide them.
537c70b8
EM
3942 *
3943 * @param array $formValues
6a488035 3944 */
00be9182 3945 public function preProcessOrderBy(&$formValues) {
6a488035 3946 // Object to show/hide form elements
ae555e90 3947 $_showHide = new CRM_Core_ShowHideBlocks('', '');
6a488035
TO
3948
3949 $_showHide->addShow('optionField_1');
3950
3951 // Cycle through order_by options; skip any empty ones, and hide them as well
3952 $n = 1;
3953
3954 if (!empty($formValues['order_bys'])) {
3955 foreach ($formValues['order_bys'] as $order_by) {
3956 if ($order_by['column'] && $order_by['column'] != '-') {
3957 $_showHide->addShow('optionField_' . $n);
3958 $orderBys[$n] = $order_by;
3959 $n++;
3960 }
3961 }
3962 }
3963 for ($i = $n; $i <= 5; $i++) {
3964 if ($i > 1) {
3965 $_showHide->addHide('optionField_' . $i);
3966 }
3967 }
3968
3969 // overwrite order_by options with modified values
3970 if (!empty($orderBys)) {
3971 $formValues['order_bys'] = $orderBys;
3972 }
3973 else {
3974 $formValues['order_bys'] = array(1 => array('column' => '-'));
3975 }
3976
3977 // assign show/hide data to template
3978 $_showHide->addToTemplate();
3979 }
3980
3981 /**
317a8023 3982 * Check if table name has columns in SELECT clause.
6a488035 3983 *
7e06c9f5
TO
3984 * @param string $tableName
3985 * Name of table (index of $this->_columns array).
6a488035
TO
3986 *
3987 * @return bool
3988 */
00be9182 3989 public function isTableSelected($tableName) {
6a488035
TO
3990 return in_array($tableName, $this->selectedTables());
3991 }
3992
568a0946 3993 /**
3994 * Check if table name has columns in WHERE or HAVING clause.
3995 *
3996 * @param string $tableName
3997 * Name of table (index of $this->_columns array).
3998 *
3999 * @return bool
4000 */
4001 public function isTableFiltered($tableName) {
4002 // Cause the array to be generated if not previously done.
4003 if (!$this->_selectedTables && !$this->filteredTables) {
4004 $this->selectedTables();
4005 }
4006 return in_array($tableName, $this->filteredTables);
4007 }
4008
6a488035 4009 /**
fe482240 4010 * Fetch array of DAO tables having columns included in SELECT or ORDER BY clause.
317a8023 4011 *
4012 * If the array is unset it will be built.
6a488035 4013 *
971d41b1
CW
4014 * @return array
4015 * selectedTables
6a488035 4016 */
00be9182 4017 public function selectedTables() {
6a488035
TO
4018 if (!$this->_selectedTables) {
4019 $orderByColumns = array();
9d72cede
EM
4020 if (array_key_exists('order_bys', $this->_params) &&
4021 is_array($this->_params['order_bys'])
4022 ) {
6a488035
TO
4023 foreach ($this->_params['order_bys'] as $orderBy) {
4024 $orderByColumns[] = $orderBy['column'];
4025 }
4026 }
4027
4028 foreach ($this->_columns as $tableName => $table) {
4029 if (array_key_exists('fields', $table)) {
4030 foreach ($table['fields'] as $fieldName => $field) {
9d72cede
EM
4031 if (!empty($field['required']) ||
4032 !empty($this->_params['fields'][$fieldName])
4033 ) {
6a488035
TO
4034 $this->_selectedTables[] = $tableName;
4035 break;
4036 }
4037 }
4038 }
4039 if (array_key_exists('order_bys', $table)) {
4040 foreach ($table['order_bys'] as $orderByName => $orderBy) {
4041 if (in_array($orderByName, $orderByColumns)) {
4042 $this->_selectedTables[] = $tableName;
4043 break;
4044 }
4045 }
4046 }
4047 if (array_key_exists('filters', $table)) {
4048 foreach ($table['filters'] as $filterName => $filter) {
3d8f552e 4049 if (!empty($this->_params["{$filterName}_value"])
4050 || !empty($this->_params["{$filterName}_relative"])
4051 || CRM_Utils_Array::value("{$filterName}_op", $this->_params) ==
4052 'nll'
4053 || CRM_Utils_Array::value("{$filterName}_op", $this->_params) ==
9d72cede 4054 'nnll'
6a488035
TO
4055 ) {
4056 $this->_selectedTables[] = $tableName;
568a0946 4057 $this->filteredTables[] = $tableName;
6a488035
TO
4058 break;
4059 }
4060 }
4061 }
4062 }
4063 }
4064 return $this->_selectedTables;
4065 }
4066
850e4640 4067 /**
317a8023 4068 * Add address fields.
4069 *
850e4640
E
4070 * @deprecated - use getAddressColumns which is a more accurate description
4071 * and also accepts an array of options rather than a long list
4072 *
c490a46a 4073 * adding address fields to construct function in reports
77b97be7 4074 *
7e06c9f5
TO
4075 * @param bool $groupBy
4076 * Add GroupBy? Not appropriate for detail report.
4077 * @param bool $orderBy
4078 * Add GroupBy? Not appropriate for detail report.
77b97be7
EM
4079 * @param bool $filters
4080 * @param array $defaults
4081 *
a6c01b45
CW
4082 * @return array
4083 * address fields for construct clause
6a488035 4084 */
00be9182 4085 public function addAddressFields($groupBy = TRUE, $orderBy = FALSE, $filters = TRUE, $defaults = array('country_id' => TRUE)) {
8b2dc0b1
PN
4086 $defaultAddressFields = array(
4087 'street_address' => ts('Street Address'),
4088 'supplemental_address_1' => ts('Supplementary Address Field 1'),
4089 'supplemental_address_2' => ts('Supplementary Address Field 2'),
207f62c6 4090 'supplemental_address_3' => ts('Supplementary Address Field 3'),
8b2dc0b1
PN
4091 'street_number' => ts('Street Number'),
4092 'street_name' => ts('Street Name'),
4093 'street_unit' => ts('Street Unit'),
4094 'city' => ts('City'),
4095 'postal_code' => ts('Postal Code'),
4096 'postal_code_suffix' => ts('Postal Code Suffix'),
4097 'country_id' => ts('Country'),
4098 'state_province_id' => ts('State/Province'),
4099 'county_id' => ts('County'),
4100 );
6a488035 4101 $addressFields = array(
9d72cede 4102 'civicrm_address' => array(
6a488035 4103 'dao' => 'CRM_Core_DAO_Address',
9d72cede 4104 'fields' => array(
e5575773 4105 'address_name' => array(
9d72cede 4106 'title' => ts('Address Name'),
6a488035 4107 'default' => CRM_Utils_Array::value('name', $defaults, FALSE),
e5575773 4108 'name' => 'name',
6a488035 4109 ),
6a488035
TO
4110 ),
4111 'grouping' => 'location-fields',
4112 ),
4113 );
8b2dc0b1
PN
4114 foreach ($defaultAddressFields as $fieldName => $fieldLabel) {
4115 $addressFields['civicrm_address']['fields'][$fieldName] = array(
4116 'title' => $fieldLabel,
4117 'default' => CRM_Utils_Array::value($fieldName, $defaults, FALSE),
4118 );
4119 }
6a488035 4120
c54d743b 4121 $street_address_filters = $general_address_filters = array();
6a488035 4122 if ($filters) {
21c21057
JV
4123 // Address filter depends on whether street address parsing is enabled.
4124 // (CRM-18696)
4125 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
4126 'address_options'
4127 );
4128 if ($addressOptions['street_address_parsing']) {
4129 $street_address_filters = array(
4130 'street_number' => array(
4131 'title' => ts('Street Number'),
8b2dc0b1 4132 'type' => CRM_Utils_Type::T_INT,
21c21057
JV
4133 'name' => 'street_number',
4134 ),
4135 'street_name' => array(
4136 'title' => ts('Street Name'),
4137 'name' => 'street_name',
8b2dc0b1 4138 'type' => CRM_Utils_Type::T_STRING,
21c21057
JV
4139 ),
4140 );
4141 }
4142 else {
4143 $street_address_filters = array(
4144 'street_address' => array(
4145 'title' => ts('Street Address'),
8b2dc0b1 4146 'type' => CRM_Utils_Type::T_STRING,
21c21057
JV
4147 'name' => 'street_address',
4148 ),
4149 );
4150 }
4151 $general_address_filters = array(
9d72cede
EM
4152 'postal_code' => array(
4153 'title' => ts('Postal Code'),
8b2dc0b1 4154 'type' => CRM_Utils_Type::T_STRING,
9d72cede 4155 'name' => 'postal_code',
6a488035 4156 ),
9d72cede
EM
4157 'city' => array(
4158 'title' => ts('City'),
8b2dc0b1 4159 'type' => CRM_Utils_Type::T_STRING,
9d72cede 4160 'name' => 'city',
6a488035 4161 ),
863581d1
CW
4162 'country_id' => array(
4163 'name' => 'country_id',
4164 'title' => ts('Country'),
6a488035 4165 'type' => CRM_Utils_Type::T_INT,
9d72cede 4166 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
c927c151 4167 'options' => CRM_Core_PseudoConstant::country(),
6a488035
TO
4168 ),
4169 'state_province_id' => array(
4170 'name' => 'state_province_id',
4171 'title' => ts('State/Province'),
4172 'type' => CRM_Utils_Type::T_INT,
c927c151
CW
4173 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
4174 'options' => array(),
6a488035 4175 ),
863581d1
CW
4176 'county_id' => array(
4177 'name' => 'county_id',
4178 'title' => ts('County'),
6a488035 4179 'type' => CRM_Utils_Type::T_INT,
c927c151
CW
4180 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
4181 'options' => array(),
6a488035
TO
4182 ),
4183 );
4184 }
21c21057
JV
4185 $addressFields['civicrm_address']['filters'] = array_merge(
4186 $street_address_filters,
4187 $general_address_filters);
6a488035
TO
4188
4189 if ($orderBy) {
9d72cede
EM
4190 $addressFields['civicrm_address']['order_bys'] = array(
4191 'street_name' => array('title' => ts('Street Name')),
ccc29f8e 4192 'street_number' => array('title' => ts('Odd / Even Street Number')),
9d72cede
EM
4193 'street_address' => NULL,
4194 'city' => NULL,
4195 'postal_code' => NULL,
6a488035
TO
4196 );
4197 }
4198
4199 if ($groupBy) {
4200 $addressFields['civicrm_address']['group_bys'] = array(
4201 'street_address' => NULL,
4202 'city' => NULL,
4203 'postal_code' => NULL,
9d72cede
EM
4204 'state_province_id' => array(
4205 'title' => ts('State/Province'),
6a488035 4206 ),
9d72cede
EM
4207 'country_id' => array(
4208 'title' => ts('Country'),
6a488035 4209 ),
9d72cede
EM
4210 'county_id' => array(
4211 'title' => ts('County'),
6a488035
TO
4212 ),
4213 );
4214 }
4215 return $addressFields;
4216 }
4217
74cf4551 4218 /**
fe482240 4219 * Do AlterDisplay processing on Address Fields.
688d37c6 4220 *
317a8023 4221 * @param array $row
4222 * @param array $rows
4223 * @param int $rowNum
4224 * @param string $baseUrl
e4e2ff09 4225 * @param string $linkText
74cf4551
EM
4226 *
4227 * @return bool
4228 */
e4e2ff09 4229 public function alterDisplayAddressFields(&$row, &$rows, &$rowNum, $baseUrl, $linkText) {
6a488035
TO
4230 $criteriaQueryParams = CRM_Report_Utils_Report::getPreviewCriteriaQueryParams($this->_defaults, $this->_params);
4231 $entryFound = FALSE;
4232 // handle country
4233 if (array_key_exists('civicrm_address_country_id', $row)) {
4234 if ($value = $row['civicrm_address_country_id']) {
4235 $rows[$rowNum]['civicrm_address_country_id'] = CRM_Core_PseudoConstant::country($value, FALSE);
cec737d8 4236 if ($baseUrl) {
4237 $url = CRM_Report_Utils_Report::getNextUrl($baseUrl,
4238 "reset=1&force=1&{$criteriaQueryParams}&" .
4239 "country_id_op=in&country_id_value={$value}",
4240 $this->_absoluteUrl, $this->_id
4241 );
4242 $rows[$rowNum]['civicrm_address_country_id_link'] = $url;
4243 $rows[$rowNum]['civicrm_address_country_id_hover'] = ts("%1 for this country.",
4244 array(1 => $linkText)
4245 );
4246 }
6a488035
TO
4247 }
4248
4249 $entryFound = TRUE;
4250 }
4251 if (array_key_exists('civicrm_address_county_id', $row)) {
4252 if ($value = $row['civicrm_address_county_id']) {
4253 $rows[$rowNum]['civicrm_address_county_id'] = CRM_Core_PseudoConstant::county($value, FALSE);
cec737d8 4254 if ($baseUrl) {
4255 $url = CRM_Report_Utils_Report::getNextUrl($baseUrl,
4256 "reset=1&force=1&{$criteriaQueryParams}&" .
4257 "county_id_op=in&county_id_value={$value}",
4258 $this->_absoluteUrl, $this->_id
4259 );
4260 $rows[$rowNum]['civicrm_address_county_id_link'] = $url;
4261 $rows[$rowNum]['civicrm_address_county_id_hover'] = ts("%1 for this county.",
4262 array(1 => $linkText)
4263 );
4264 }
6a488035
TO
4265 }
4266 $entryFound = TRUE;
4267 }
4268 // handle state province
4269 if (array_key_exists('civicrm_address_state_province_id', $row)) {
4270 if ($value = $row['civicrm_address_state_province_id']) {
4271 $rows[$rowNum]['civicrm_address_state_province_id'] = CRM_Core_PseudoConstant::stateProvince($value, FALSE);
cec737d8 4272 if ($baseUrl) {
4273 $url = CRM_Report_Utils_Report::getNextUrl($baseUrl,
4274 "reset=1&force=1&{$criteriaQueryParams}&state_province_id_op=in&state_province_id_value={$value}",
4275 $this->_absoluteUrl, $this->_id
4276 );
4277 $rows[$rowNum]['civicrm_address_state_province_id_link'] = $url;
4278 $rows[$rowNum]['civicrm_address_state_province_id_hover'] = ts("%1 for this state.",
4279 array(1 => $linkText)
4280 );
4281 }
6a488035
TO
4282 }
4283 $entryFound = TRUE;
4284 }
4285
4286 return $entryFound;
4287 }
4288
e5575773 4289 /**
4290 * Do AlterDisplay processing on Address Fields.
4291 *
4292 * @param array $row
4293 * @param array $rows
4294 * @param int $rowNum
e4e2ff09 4295 * @param string $baseUrl
4296 * @param string $linkText
e5575773 4297 *
4298 * @return bool
4299 */
e4e2ff09 4300 public function alterDisplayContactFields(&$row, &$rows, &$rowNum, $baseUrl, $linkText) {
e5575773 4301 $entryFound = FALSE;
d9bf3c53 4302 // There is no reason not to add links for all fields but it seems a bit odd to be able to click on
4303 // 'Mrs'. Also, we don't have metadata about the title. So, add selectively to addLinks.
4304 $addLinks = array('gender_id' => 'Gender');
b706a634 4305 foreach (array('prefix_id', 'suffix_id', 'gender_id', 'contact_sub_type', 'preferred_language') as $fieldName) {
50172d25 4306 if (array_key_exists('civicrm_contact_' . $fieldName, $row)) {
4307 if (($value = $row['civicrm_contact_' . $fieldName]) != FALSE) {
b706a634 4308 $rowValues = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
4309 $rowLabels = array();
4310 foreach ($rowValues as $rowValue) {
4311 if ($rowValue) {
074dd766 4312 $rowLabels[] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $fieldName, $rowValue);
b706a634 4313 }
4314 }
4315 $rows[$rowNum]['civicrm_contact_' . $fieldName] = implode(', ', $rowLabels);
cec737d8 4316 if ($baseUrl && ($title = CRM_Utils_Array::value($fieldName, $addLinks)) != FALSE) {
d9bf3c53 4317 $this->addLinkToRow($rows[$rowNum], $baseUrl, $linkText, $value, $fieldName, 'civicrm_contact', $title);
4318 }
50172d25 4319 }
4320 $entryFound = TRUE;
e5575773 4321 }
e5575773 4322 }
ea477981 4323 $yesNoFields = array(
4324 'do_not_email', 'is_deceased', 'do_not_phone', 'do_not_sms', 'do_not_mail', 'is_opt_out',
4325 );
4326 foreach ($yesNoFields as $fieldName) {
4327 if (array_key_exists('civicrm_contact_' . $fieldName, $row)) {
4328 // Since these are essentially 'negative fields' it feels like it
4329 // makes sense to only highlight the exceptions hence no 'No'.
4330 $rows[$rowNum]['civicrm_contact_' . $fieldName] = !empty($rows[$rowNum]['civicrm_contact_' . $fieldName]) ? ts('Yes') : '';
4331 $entryFound = TRUE;
4332 }
4333 }
e5575773 4334 return $entryFound;
4335 }
4336
74cf4551 4337 /**
688d37c6
CW
4338 * Adjusts dates passed in to YEAR() for fiscal year.
4339 *
100fef9d 4340 * @param string $fieldName
74cf4551
EM
4341 *
4342 * @return string
4343 */
00be9182 4344 public function fiscalYearOffset($fieldName) {
6a488035
TO
4345 $config = CRM_Core_Config::singleton();
4346 $fy = $config->fiscalYearStart;
9d72cede
EM
4347 if (CRM_Utils_Array::value('yid_op', $this->_params) == 'calendar' ||
4348 ($fy['d'] == 1 && $fy['M'] == 1)
4349 ) {
6a488035
TO
4350 return "YEAR( $fieldName )";
4351 }
9d72cede
EM
4352 return "YEAR( $fieldName - INTERVAL " . ($fy['M'] - 1) . " MONTH" .
4353 ($fy['d'] > 1 ? (" - INTERVAL " . ($fy['d'] - 1) . " DAY") : '') . " )";
6a488035
TO
4354 }
4355
688d37c6 4356 /**
fe482240 4357 * Add Address into From Table if required.
18f511e2 4358 *
4359 * @deprecated use joinAddressFromContact
4360 * (left here in case extensions use it).
6a488035 4361 */
00be9182 4362 public function addAddressFromClause() {
18f511e2 4363 Civi::log()->warning('Deprecated function addAddressFromClause. Use joinAddressFromContact.', array('civi.tag' => 'deprecated'));
6a488035
TO
4364 // include address field if address column is to be included
4365 if ((isset($this->_addressField) &&
4366 $this->_addressField
4367 ) ||
4368 $this->isTableSelected('civicrm_address')
4369 ) {
4370 $this->_from .= "
4371 LEFT JOIN civicrm_address {$this->_aliases['civicrm_address']}
4372 ON ({$this->_aliases['civicrm_contact']}.id =
4373 {$this->_aliases['civicrm_address']}.contact_id) AND
4374 {$this->_aliases['civicrm_address']}.is_primary = 1\n";
4375 }
4376 }
4377
850e4640 4378 /**
fe482240 4379 * Add Phone into From Table if required.
18f511e2 4380 *
4381 * @deprecated use joinPhoneFromContact
4382 * (left here in case extensions use it).
850e4640 4383 */
00be9182 4384 public function addPhoneFromClause() {
18f511e2 4385
4386 Civi::log()->warning('Deprecated function addPhoneFromClause. Use joinPhoneFromContact.', array('civi.tag' => 'deprecated'));
850e4640 4387 // include address field if address column is to be included
d2a1da52 4388 if ($this->isTableSelected('civicrm_phone')) {
850e4640
E
4389 $this->_from .= "
4390 LEFT JOIN civicrm_phone {$this->_aliases['civicrm_phone']}
4391 ON ({$this->_aliases['civicrm_contact']}.id =
4392 {$this->_aliases['civicrm_phone']}.contact_id) AND
4393 {$this->_aliases['civicrm_phone']}.is_primary = 1\n";
4394 }
4395 }
4396
e3c74b61 4397 /**
4398 * Add Address into From Table if required.
4399 *
4400 * Prefix will be added to both tables as
4401 * it is assumed you are using it to get address of a secondary contact.
4402 *
4403 * @param string $prefix
4404 * @param array $extra Additional options.
4405 * Not currently used in core but may be used in override extensions.
4406 */
4407 protected function joinAddressFromContact($prefix = '', $extra = array()) {
4408 $addressTables = ['civicrm_address', 'civicrm_country', 'civicrm_worldregion', 'civicrm_state_province'];
4409 $isJoinRequired = $this->_addressField;
4410 foreach ($addressTables as $addressTable) {
4411 if ($this->isTableSelected($prefix . $addressTable)) {
4412 $isJoinRequired = TRUE;
4413 }
4414 }
4415 if ($isJoinRequired) {
4416 $this->_from .= "
4417 LEFT JOIN civicrm_address {$this->_aliases[$prefix . 'civicrm_address']}
4418 ON ({$this->_aliases[$prefix . 'civicrm_contact']}.id =
4419 {$this->_aliases[$prefix . 'civicrm_address']}.contact_id) AND
4420 {$this->_aliases[$prefix . 'civicrm_address']}.is_primary = 1\n";
4421 }
4422 }
4423
4424 /**
4425 * Add Country into From Table if required.
4426 *
4427 * Prefix will be added to both tables as
4428 * it is assumed you are using it to get address of a secondary contact.
4429 *
4430 * @param string $prefix
4431 * @param array $extra Additional options.
4432 * Not currently used in core but may be used in override extensions.
4433 */
4434 protected function joinCountryFromAddress($prefix = '', $extra = array()) {
4435 // include country field if country column is to be included
6d81d64a 4436 if ($this->isTableSelected($prefix . 'civicrm_country') || $this->isTableSelected($prefix . 'civicrm_worldregion')) {
4437 if (empty($this->_aliases[$prefix . 'civicrm_country'])) {
4438 $this->_aliases[$prefix . 'civicrm_country'] = $prefix . '_report_country';
4439 }
e3c74b61 4440 $this->_from .= "
4441 LEFT JOIN civicrm_country {$this->_aliases[$prefix . 'civicrm_country']}
4442 ON {$this->_aliases[$prefix . 'civicrm_address']}.country_id = {$this->_aliases[$prefix . 'civicrm_country']}.id AND
4443 {$this->_aliases[$prefix . 'civicrm_address']}.is_primary = 1 ";
4444 }
4445 }
4446
4447 /**
4448 * Add Phone into From Table if required.
4449 *
4450 * Prefix will be added to both tables as
4451 * it is assumed you are using it to get address of a secondary contact.
4452 *
4453 * @param string $prefix
4454 * @param array $extra Additional options.
4455 * Not currently used in core but may be used in override extensions.
4456 */
4457 protected function joinPhoneFromContact($prefix = '', $extra = array()) {
4458 // include phone field if phone column is to be included
4459 if ($this->isTableSelected($prefix . 'civicrm_phone')) {
4460 $this->_from .= "
4461 LEFT JOIN civicrm_phone {$this->_aliases[$prefix . 'civicrm_phone']}
4462 ON {$this->_aliases[$prefix . 'civicrm_contact']}.id = {$this->_aliases[$prefix . 'civicrm_phone']}.contact_id AND
4463 {$this->_aliases[$prefix . 'civicrm_phone']}.is_primary = 1\n";
4464 }
4465 }
4466
4467 /**
4468 * Add Email into From Table if required.
4469 *
4470 * Prefix will be added to both tables as
4471 * it is assumed you are using it to get address of a secondary contact.
4472 *
4473 * @param string $prefix
4474 * @param array $extra Additional options.
4475 * Not currently used in core but may be used in override extensions.
4476 */
4477 protected function joinEmailFromContact($prefix = '', $extra = array()) {
4478 // include email field if email column is to be included
4479 if ($this->isTableSelected($prefix . 'civicrm_email')) {
4480 $this->_from .= "
4481 LEFT JOIN civicrm_email {$this->_aliases[$prefix . 'civicrm_email']}
4482 ON ({$this->_aliases[$prefix . 'civicrm_contact']}.id = {$this->_aliases[$prefix . 'civicrm_email']}.contact_id AND
4483 {$this->_aliases[$prefix . 'civicrm_email']}.is_primary = 1) ";
4484 }
4485 }
4486
d2a1da52
ERL
4487 /**
4488 * Add Financial Transaction into From Table if required.
4489 */
4490 public function addFinancialTrxnFromClause() {
4491 if ($this->isTableSelected('civicrm_financial_trxn')) {
4492 $this->_from .= "
4493 LEFT JOIN civicrm_entity_financial_trxn eftcc
4494 ON ({$this->_aliases['civicrm_contribution']}.id = eftcc.entity_id AND
4495 eftcc.entity_table = 'civicrm_contribution')
4496 LEFT JOIN civicrm_financial_trxn {$this->_aliases['civicrm_financial_trxn']}
4497 ON {$this->_aliases['civicrm_financial_trxn']}.id = eftcc.financial_trxn_id \n";
4498 }
4499 }
4500
850e4640 4501 /**
fe482240 4502 * Get phone columns to add to array.
9d72cede 4503 *
850e4640 4504 * @param array $options
16b10e64
CW
4505 * - prefix Prefix to add to table (in case of more than one instance of the table)
4506 * - prefix_label Label to give columns from this phone table instance
9d72cede 4507 *
a6c01b45
CW
4508 * @return array
4509 * phone columns definition
850e4640 4510 */
00be9182 4511 public function getPhoneColumns($options = array()) {
850e4640
E
4512 $defaultOptions = array(
4513 'prefix' => '',
4514 'prefix_label' => '',
4515 );
4516
9d72cede 4517 $options = array_merge($defaultOptions, $options);
850e4640
E
4518
4519 $fields = array(
4520 $options['prefix'] . 'civicrm_phone' => array(
9d72cede 4521 'dao' => 'CRM_Core_DAO_Phone',
850e4640
E
4522 'fields' => array(
4523 $options['prefix'] . 'phone' => array(
c576cb65 4524 'title' => $options['prefix_label'] . ts('Phone'),
21dfd5f5 4525 'name' => 'phone',
850e4640
E
4526 ),
4527 ),
4528 ),
4529 );
4530 return $fields;
4531 }
4532
4533 /**
fe482240 4534 * Get address columns to add to array.
9d72cede 4535 *
850e4640 4536 * @param array $options
16b10e64
CW
4537 * - prefix Prefix to add to table (in case of more than one instance of the table)
4538 * - prefix_label Label to give columns from this address table instance
9d72cede 4539 *
a6c01b45
CW
4540 * @return array
4541 * address columns definition
850e4640 4542 */
00be9182 4543 public function getAddressColumns($options = array()) {
c927c151 4544 $options += array(
850e4640
E
4545 'prefix' => '',
4546 'prefix_label' => '',
4547 'group_by' => TRUE,
4548 'order_by' => TRUE,
4549 'filters' => TRUE,
c927c151 4550 'defaults' => array(),
850e4640 4551 );
850e4640
E
4552 return $this->addAddressFields(
4553 $options['group_by'],
4554 $options['order_by'],
4555 $options['filters'],
4556 $options['defaults']
4557 );
850e4640
E
4558 }
4559
e5575773 4560 /**
4561 * Get a standard set of contact fields.
4562 *
4563 * @return array
4564 */
4565 public function getBasicContactFields() {
4566 return array(
4567 'sort_name' => array(
4568 'title' => ts('Contact Name'),
4569 'required' => TRUE,
4570 'default' => TRUE,
4571 ),
4572 'id' => array(
4573 'no_display' => TRUE,
4574 'required' => TRUE,
4575 ),
4576 'prefix_id' => array(
4577 'title' => ts('Contact Prefix'),
4578 ),
4579 'first_name' => array(
4580 'title' => ts('First Name'),
4581 ),
4120c775 4582 'nick_name' => array(
4583 'title' => ts('Nick Name'),
4584 ),
e5575773 4585 'middle_name' => array(
4586 'title' => ts('Middle Name'),
4587 ),
4588 'last_name' => array(
4589 'title' => ts('Last Name'),
4590 ),
4591 'suffix_id' => array(
4592 'title' => ts('Contact Suffix'),
4593 ),
4594 'postal_greeting_display' => array('title' => ts('Postal Greeting')),
4595 'email_greeting_display' => array('title' => ts('Email Greeting')),
a703d90c 4596 'addressee_display' => array('title' => ts('Addressee')),
e5575773 4597 'contact_type' => array(
4598 'title' => ts('Contact Type'),
4599 ),
4600 'contact_sub_type' => array(
4601 'title' => ts('Contact Subtype'),
4602 ),
4603 'gender_id' => array(
4604 'title' => ts('Gender'),
4605 ),
4606 'birth_date' => array(
4607 'title' => ts('Birth Date'),
4608 ),
4609 'age' => array(
4610 'title' => ts('Age'),
4611 'dbAlias' => 'TIMESTAMPDIFF(YEAR, contact_civireport.birth_date, CURDATE())',
4612 ),
4613 'job_title' => array(
4614 'title' => ts('Contact Job title'),
4615 ),
4616 'organization_name' => array(
4617 'title' => ts('Organization Name'),
4618 ),
b4d1eebe 4619 'external_identifier' => array(
4620 'title' => ts('Contact identifier from external system'),
4621 ),
ea477981 4622 'do_not_email' => array(),
4623 'do_not_phone' => array(),
4624 'do_not_mail' => array(),
4625 'do_not_sms' => array(),
4626 'is_opt_out' => array(),
4627 'is_deceased' => array(),
b706a634 4628 'preferred_language' => array(),
e5575773 4629 );
4630 }
4631
aceb083a 4632 /**
4633 * Get a standard set of contact filters.
4634 *
4635 * @return array
4636 */
4637 public function getBasicContactFilters() {
4638 return array(
4639 'sort_name' => array(
4640 'title' => ts('Contact Name'),
4641 ),
4642 'source' => array(
4643 'title' => ts('Contact Source'),
4644 'type' => CRM_Utils_Type::T_STRING,
4645 ),
4646 'id' => array(
4647 'title' => ts('Contact ID'),
4648 'no_display' => TRUE,
4649 ),
4650 'gender_id' => array(
4651 'title' => ts('Gender'),
4652 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
4653 'options' => CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id'),
4654 ),
4655 'birth_date' => array(
4656 'title' => ts('Birth Date'),
4657 'operatorType' => CRM_Report_Form::OP_DATE,
4658 ),
4659 'contact_type' => array(
4660 'title' => ts('Contact Type'),
4661 ),
4662 'contact_sub_type' => array(
4663 'title' => ts('Contact Subtype'),
4664 ),
4665 'modified_date' => array(
4666 'title' => ts('Contact Modified'),
4667 'operatorType' => CRM_Report_Form::OP_DATE,
4668 'type' => CRM_Utils_Type::T_DATE,
4669 ),
4670 'is_deceased' => array(
4671 'title' => ts('Deceased'),
4672 'type' => CRM_Utils_Type::T_BOOLEAN,
4673 'default' => 0,
4674 ),
b3f2f5e9 4675 'do_not_email' => array(
4676 'title' => ts('Do not email'),
4677 'type' => CRM_Utils_Type::T_BOOLEAN,
4678 ),
4679 'do_not_phone' => array(
4680 'title' => ts('Do not phone'),
4681 'type' => CRM_Utils_Type::T_BOOLEAN,
4682 ),
4683 'do_not_mail' => array(
4684 'title' => ts('Do not mail'),
4685 'type' => CRM_Utils_Type::T_BOOLEAN,
4686 ),
4687 'do_not_sms' => array(
4688 'title' => ts('Do not SMS'),
4689 'type' => CRM_Utils_Type::T_BOOLEAN,
4690 ),
4691 'is_opt_out' => array(
4692 'title' => ts('Do not bulk email'),
4693 'type' => CRM_Utils_Type::T_BOOLEAN,
4694 ),
b706a634 4695 'preferred_language' => array(
4696 'title' => ts('Preferred Language'),
4697 ),
da7ac680
CW
4698 'is_deleted' => array(
4699 'no_display' => TRUE,
4700 'default' => 0,
4701 'type' => CRM_Utils_Type::T_BOOLEAN,
4702 ),
aceb083a 4703 );
4704 }
4705
74cf4551 4706 /**
317a8023 4707 * Add contact to group.
4708 *
100fef9d 4709 * @param int $groupID
74cf4551 4710 */
00be9182 4711 public function add2group($groupID) {
6a488035
TO
4712 if (is_numeric($groupID) && isset($this->_aliases['civicrm_contact'])) {
4713 $select = "SELECT DISTINCT {$this->_aliases['civicrm_contact']}.id AS addtogroup_contact_id, ";
f0b5b73e 4714 $select = preg_replace('/SELECT(\s+SQL_CALC_FOUND_ROWS)?\s+/i', $select, $this->_select);
6a488035 4715 $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy}";
e05e0dba 4716 $sql = str_replace('WITH ROLLUP', '', $sql);
6a488035
TO
4717 $dao = CRM_Core_DAO::executeQuery($sql);
4718
4719 $contact_ids = array();
4720 // Add resulting contacts to group
4721 while ($dao->fetch()) {
4722 if ($dao->addtogroup_contact_id) {
4723 $contact_ids[$dao->addtogroup_contact_id] = $dao->addtogroup_contact_id;
4724 }
4725 }
4726
9d72cede 4727 if (!empty($contact_ids)) {
6a488035
TO
4728 CRM_Contact_BAO_GroupContact::addContactsToGroup($contact_ids, $groupID);
4729 CRM_Core_Session::setStatus(ts("Listed contact(s) have been added to the selected group."), ts('Contacts Added'), 'success');
4730 }
4731 else {
4732 CRM_Core_Session::setStatus(ts("The listed records(s) cannot be added to the group."));
4733 }
4734 }
4735 }
46065582 4736
688d37c6 4737 /**
317a8023 4738 * Show charts on print screen.
688d37c6 4739 */
00be9182 4740 public static function uploadChartImage() {
46065582 4741 // upload strictly for '.png' images
d04d4eef
PJ
4742 $name = trim(basename(CRM_Utils_Request::retrieve('name', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET')));
4743 if (preg_match('/\.png$/', $name)) {
053cbadd
MM
4744
4745 // Get the RAW .png from the input.
4746 $httpRawPostData = file_get_contents("php://input");
46065582
PJ
4747
4748 // prepare the directory
4749 $config = CRM_Core_Config::singleton();
971d41b1
CW
4750 $defaultPath
4751 = str_replace('/persist/contribute/', '/persist/', $config->imageUploadDir) .
9d72cede 4752 '/openFlashChart/';
46065582
PJ
4753 if (!file_exists($defaultPath)) {
4754 mkdir($defaultPath, 0777, TRUE);
4755 }
4756
4757 // full path to the saved image including filename
d04d4eef 4758 $destination = $defaultPath . $name;
46065582
PJ
4759
4760 //write and save
4761 $jfh = fopen($destination, 'w') or die("can't open file");
4762 fwrite($jfh, $httpRawPostData);
4763 fclose($jfh);
4764 CRM_Utils_System::civiExit();
4765 }
4766 }
2107cde9
CW
4767
4768 /**
fe482240 4769 * Apply common settings to entityRef fields.
9d72cede 4770 *
2107cde9
CW
4771 * @param array $field
4772 * @param string $table
4773 */
4774 private function setEntityRefDefaults(&$field, $table) {
4775 $field['attributes'] = $field['attributes'] ? $field['attributes'] : array();
4776 $field['attributes'] += array(
4777 'entity' => CRM_Core_DAO_AllCoreTables::getBriefName(CRM_Core_DAO_AllCoreTables::getClassForTable($table)),
4778 'multiple' => TRUE,
4779 'placeholder' => ts('- select -'),
4780 );
4781 }
96025800 4782
e4e2ff09 4783 /**
4784 * Add link fields to the row.
4785 *
4786 * Function adds the _link & _hover fields to the row.
4787 *
4788 * @param array $row
4789 * @param string $baseUrl
4790 * @param string $linkText
4791 * @param string $value
4792 * @param string $fieldName
4793 * @param string $tablePrefix
4794 * @param string $fieldLabel
4795 *
4796 * @return mixed
4797 */
4798 protected function addLinkToRow(&$row, $baseUrl, $linkText, $value, $fieldName, $tablePrefix, $fieldLabel) {
4799 $criteriaQueryParams = CRM_Report_Utils_Report::getPreviewCriteriaQueryParams($this->_defaults, $this->_params);
4800 $url = CRM_Report_Utils_Report::getNextUrl($baseUrl,
4801 "reset=1&force=1&{$criteriaQueryParams}&" .
4802 $fieldName . "_op=in&{$fieldName}_value={$value}",
4803 $this->_absoluteUrl, $this->_id
4804 );
4805 $row["{$tablePrefix}_{$fieldName}_link"] = $url;
4806 $row["{$tablePrefix}_{$fieldName}_hover"] = ts("%1 for this %2.",
4807 array(1 => $linkText, 2 => $fieldLabel)
4808 );
4809 }
4810
182f5081 4811 /**
4812 * Get label for show results buttons.
4813 *
4814 * @return string
4815 */
4816 public function getResultsLabel() {
4817 $showResultsLabel = $this->resultsDisplayed() ? ts('Refresh results') : ts('View results');
4818 return $showResultsLabel;
4819 }
4820
4821 /**
4822 * Determine the output mode from the url or input.
4823 *
4824 * Output could be
4825 * - pdf : Render as pdf
4826 * - csv : Render as csv
4827 * - print : Render in print format
4828 * - save : save the report and display the new report
4829 * - copy : save the report as a new instance and display that.
4830 * - group : go to the add to group screen.
4831 *
4832 * Potentially chart variations could also be included but the complexity
4833 * is that we might print a bar chart as a pdf.
4834 */
4835 protected function setOutputMode() {
1a7356e7 4836 $this->_outputMode = str_replace('report_instance.', '', CRM_Utils_Request::retrieve(
182f5081 4837 'output',
4838 'String',
4839 CRM_Core_DAO::$_nullObject,
4840 FALSE,
4841 CRM_Utils_Array::value('task', $this->_params)
44543184 4842 ));
7343d8a7 4843 // if contacts are added to group
4844 if (!empty($this->_params['groups']) && empty($this->_outputMode)) {
4845 $this->_outputMode = 'group';
4846 }
88fefc2f 4847 if (isset($this->_params['task'])) {
4848 unset($this->_params['task']);
4849 }
182f5081 4850 }
4851
0fefbf7f 4852 /**
ecf1e543 4853 * CRM-17793 - Alter DateTime section header to group by date from the datetime field.
3b6d61f2 4854 *
ecf1e543 4855 * @param $tempTable
4856 * @param $columnName
4857 */
4858 public function alterSectionHeaderForDateTime($tempTable, $columnName) {
0fefbf7f 4859 // add new column with date value for the datetime field
ecf1e543 4860 $tempQuery = "ALTER TABLE {$tempTable} ADD COLUMN {$columnName}_date VARCHAR(128)";
4861 CRM_Core_DAO::executeQuery($tempQuery);
4862 $updateQuery = "UPDATE {$tempTable} SET {$columnName}_date = date({$columnName})";
4863 CRM_Core_DAO::executeQuery($updateQuery);
b708c08d 4864 $this->_selectClauses[] = "{$columnName}_date";
ecf1e543 4865 $this->_select .= ", {$columnName}_date";
4866 $this->_sections["{$columnName}_date"] = $this->_sections["{$columnName}"];
4867 unset($this->_sections["{$columnName}"]);
4868 $this->assign('sections', $this->_sections);
4869 }
4870
55f71fa7 4871 /**
4872 * Get an array of the columns that have been selected for display.
4873 *
4874 * @return array
4875 */
4876 public function getSelectColumns() {
4877 $selectColumns = array();
4878 foreach ($this->_columns as $tableName => $table) {
4879 if (array_key_exists('fields', $table)) {
4880 foreach ($table['fields'] as $fieldName => $field) {
4881 if (!empty($field['required']) ||
4882 !empty($this->_params['fields'][$fieldName])
4883 ) {
4884
4885 $selectColumns["{$tableName}_{$fieldName}"] = 1;
4886 }
4887 }
4888 }
4889 }
4890 return $selectColumns;
4891 }
4892
c160fde8 4893 /**
4894 * Add location tables to the query if they are used for filtering.
4895 *
4896 * This is for when we are running the query separately for filtering and retrieving display fields.
4897 */
4898 public function selectivelyAddLocationTablesJoinsToFilterQuery() {
4899 if ($this->isTableFiltered('civicrm_email')) {
4900 $this->_from .= "
4901 LEFT JOIN civicrm_email {$this->_aliases['civicrm_email']}
4902 ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_email']}.contact_id
4903 AND {$this->_aliases['civicrm_email']}.is_primary = 1";
4904 }
4905 if ($this->isTableFiltered('civicrm_phone')) {
4906 $this->_from .= "
4907 LEFT JOIN civicrm_phone {$this->_aliases['civicrm_phone']}
4908 ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_phone']}.contact_id
4909 AND {$this->_aliases['civicrm_phone']}.is_primary = 1";
4910 }
4911 if ($this->isTableFiltered('civicrm_address')) {
4912 $this->_from .= "
4913 LEFT JOIN civicrm_address {$this->_aliases['civicrm_address']}
4914 ON ({$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_address']}.contact_id)
4915 AND {$this->_aliases['civicrm_address']}.is_primary = 1\n";
4916 }
4917 }
4918
43c1fa19 4919 /**
4920 * Set the base table for the FROM clause.
4921 *
4922 * Sets up the from clause, allowing for the possibility it might be a
4923 * temp table pre-filtered by groups if a group filter is in use.
4924 *
4925 * @param string $baseTable
4926 * @param string $field
4927 * @param null $tableAlias
4928 */
4929 public function setFromBase($baseTable, $field = 'id', $tableAlias = NULL) {
4930 if (!$tableAlias) {
4931 $tableAlias = $this->_aliases[$baseTable];
4932 }
4933 $this->_from = $this->_from = " FROM $baseTable $tableAlias ";
4934 $this->joinGroupTempTable($baseTable, $field, $tableAlias);
4935 $this->_from .= " {$this->_aclFrom} ";
4936 }
4937
4938 /**
4939 * Join the temp table contacting contacts who are members of the filtered groups.
4940 *
4941 * If we are using an IN filter we use an inner join, otherwise a left join.
4942 *
4943 * @param string $baseTable
4944 * @param string $field
4945 * @param string $tableAlias
4946 */
4947 public function joinGroupTempTable($baseTable, $field, $tableAlias) {
4948 if ($this->groupTempTable) {
4949 if ($this->_params['gid_op'] == 'in') {
4950 $this->_from = " FROM $this->groupTempTable group_temp_table INNER JOIN $baseTable $tableAlias
4951 ON group_temp_table.id = $tableAlias.{$field} ";
4952 }
4953 else {
4954 $this->_from .= "
4955 LEFT JOIN $this->groupTempTable group_temp_table
4956 ON $tableAlias.{$field} = group_temp_table.id ";
4957 }
4958 }
4959 }
4960
fa5fb88c 4961 /**
a51858a1 4962 * Get all labels for fields that are used in a group concat.
fa5fb88c 4963 *
a51858a1
E
4964 * @param string $options
4965 * comma separated option values.
4966 * @param string $baoName
4967 * The BAO name for the field.
4968 * @param string $fieldName
4969 * The name of the field for which labels should be retrieved.
4970 *
4971 * return string
fa5fb88c 4972 */
a51858a1
E
4973 public function getLabels($options, $baoName, $fieldName) {
4974 $types = explode(',', $options);
fa5fb88c
E
4975 $labels = array();
4976 foreach ($types as $value) {
a51858a1 4977 $labels[$value] = CRM_Core_PseudoConstant::getLabel($baoName, $fieldName, $value);
fa5fb88c
E
4978 }
4979 return implode(', ', array_filter($labels));
4980 }
4981
074dd766 4982 /**
0a01dff9 4983 * Add statistics columns.
4984 *
4985 * If a group by is in play then add columns for the statistics fields.
4986 *
4987 * This would lead to a new field in the $row such as $fieldName_sum and a new, matching
4988 * column header field.
4989 *
4990 * @param array $field
4991 * @param string $tableName
4992 * @param string $fieldName
4993 * @param array $select
4994 *
4995 * @return array
4996 */
4997 protected function addStatisticsToSelect($field, $tableName, $fieldName, $select) {
4998 foreach ($field['statistics'] as $stat => $label) {
4999 $alias = "{$tableName}_{$fieldName}_{$stat}";
5000 switch (strtolower($stat)) {
5001 case 'max':
5002 case 'sum':
5003 $select[] = "$stat({$field['dbAlias']}) as $alias";
5004 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
5005 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
5006 $this->_statFields[$label] = $alias;
5007 $this->_selectAliases[] = $alias;
5008 break;
5009
5010 case 'count':
5011 $select[] = "COUNT({$field['dbAlias']}) as $alias";
5012 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
5013 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = CRM_Utils_Type::T_INT;
5014 $this->_statFields[$label] = $alias;
5015 $this->_selectAliases[] = $alias;
5016 break;
5017
5018 case 'count_distinct':
5019 $select[] = "COUNT(DISTINCT {$field['dbAlias']}) as $alias";
5020 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
5021 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = CRM_Utils_Type::T_INT;
5022 $this->_statFields[$label] = $alias;
5023 $this->_selectAliases[] = $alias;
5024 break;
5025
5026 case 'avg':
5027 $select[] = "ROUND(AVG({$field['dbAlias']}),2) as $alias";
5028 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['title'] = $label;
5029 $this->_columnHeaders["{$tableName}_{$fieldName}_{$stat}"]['type'] = $field['type'];
5030 $this->_statFields[$label] = $alias;
5031 $this->_selectAliases[] = $alias;
5032 break;
5033 }
5034 }
5035 return $select;
5036 }
5037
5038 /**
5039 * Add a basic field to the select clause.
5040 *
5041 * @param string $tableName
5042 * @param string $fieldName
5043 * @param array $field
5044 * @param string $select
5045 * @return array
5046 */
5047 protected function addBasicFieldToSelect($tableName, $fieldName, $field, $select) {
5048 $alias = "{$tableName}_{$fieldName}";
5049 $select[] = "{$field['dbAlias']} as $alias";
5050 $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = CRM_Utils_Array::value('title', $field);
5051 $this->_columnHeaders["{$tableName}_{$fieldName}"]['type'] = CRM_Utils_Array::value('type', $field);
5052 $this->_selectAliases[] = $alias;
5053 return $select;
5054 }
5055
8bb36676 5056 /**
5057 * Set table alias.
5058 *
5059 * @param array $table
5060 * @param string $tableName
5061 *
5062 * @return string
5063 * Alias for table.
5064 */
5065 protected function setTableAlias($table, $tableName) {
5066 if (!isset($table['alias'])) {
5067 $this->_columns[$tableName]['alias'] = substr($tableName, 8) .
5068 '_civireport';
5069 }
5070 else {
5071 $this->_columns[$tableName]['alias'] = $table['alias'] . '_civireport';
5072 }
5073
5074 $this->_aliases[$tableName] = $this->_columns[$tableName]['alias'];
5075 return $this->_aliases[$tableName];
5076 }
5077
d7e34fc6 5078 /**
5079 * Function to add columns to reports.
5080 *
5081 * This is ported from extended reports, which also adds join filters to the options.
5082 *
5083 * @param string $type
5084 * @param array $options
5085 * - prefix - A string to prepend to the table name
5086 * - prefix_label A string to prepend to the fields
5087 * - fields (bool) - should the fields for this table be made available
5088 * - group_by (bool) - should the group bys for this table be made available.
5089 * - order_by (bool) - should the group bys for this table be made available.
5090 * - filters (bool) - should the filters for this table by made available.
5091 * - fields_defaults (array) array of fields that should be displayed by default.
5092 * - filters_defaults (array) array of fields that should be filtered by default.
5093 * - join_filters (array) fields available for filtering joins (requires additional custom code).
5094 * - join_fields (array) fields available from join (requires additional custom code).
5095 * - group_by_defaults (array) array of group bys that should be applied by default.
5096 * - order_by_defaults (array) array of order bys that should be applied by default.
5097 * - custom_fields (array) array of entity types for custom fields (not usually required).
5098 * - contact_type (string) optional restriction on contact type for some tables.
5099 * - fields_excluded (array) fields that are in the generic set for the table but not in the report.
5100 *
5101 * @return array
5102 */
5103 protected function getColumns($type, $options = array()) {
5104 $defaultOptions = array(
5105 'prefix' => '',
5106 'prefix_label' => '',
5107 'fields' => TRUE,
5108 'group_bys' => FALSE,
5109 'order_bys' => TRUE,
5110 'filters' => TRUE,
5111 'join_filters' => FALSE,
5112 'fields_defaults' => array(),
5113 'filters_defaults' => array(),
5114 'group_bys_defaults' => array(),
5115 'order_bys_defaults' => array(),
5116 );
5117 $options = array_merge($defaultOptions, $options);
5118
5119 $fn = 'get' . $type . 'Columns';
5120 return $this->$fn($options);
5121 }
5122
5123 /**
5124 * Get columns for contact table.
5125 *
5126 * @param array $options
5127 *
5128 * @return array
5129 */
5130 protected function getContactColumns($options = array()) {
5131 $defaultOptions = array(
5132 'custom_fields' => array('Individual', 'Contact', 'Organization'),
5133 'fields_defaults' => array('display_name', 'id'),
5134 'order_bys_defaults' => array('sort_name ASC'),
5135 'contact_type' => NULL,
5136 );
5137
5138 $options = array_merge($defaultOptions, $options);
5139
5140 $tableAlias = $options['prefix'] . 'contact';
5141
5142 $spec = array(
5143 $options['prefix'] . 'display_name' => array(
5144 'name' => 'display_name',
c576cb65 5145 'title' => $options['prefix_label'] . ts('Contact Name'),
d7e34fc6 5146 'is_fields' => TRUE,
5147 ),
5148 $options['prefix'] . 'sort_name' => array(
5149 'name' => 'sort_name',
c576cb65 5150 'title' => $options['prefix_label'] . ts('Contact Name (in sort format)'),
d7e34fc6 5151 'is_fields' => TRUE,
5152 'is_filters' => TRUE,
5153 'is_order_bys' => TRUE,
5154 ),
5155 $options['prefix'] . 'id' => array(
5156 'name' => 'id',
c576cb65 5157 'title' => $options['prefix_label'] . ts('Contact ID'),
d7e34fc6 5158 'alter_display' => 'alterContactID',
5159 'type' => CRM_Utils_Type::T_INT,
5160 'is_order_bys' => TRUE,
5161 'is_group_bys' => TRUE,
5162 'is_fields' => TRUE,
5163 ),
5164 $options['prefix'] . 'external_identifier' => array(
5165 'name' => 'external_identifier',
c576cb65 5166 'title' => $options['prefix_label'] . ts('External ID'),
d7e34fc6 5167 'type' => CRM_Utils_Type::T_INT,
5168 'is_fields' => TRUE,
5169 ),
5170 $options['prefix'] . 'contact_type' => array(
c576cb65 5171 'title' => $options['prefix_label'] . ts('Contact Type'),
d7e34fc6 5172 'name' => 'contact_type',
5173 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
5174 'options' => CRM_Contact_BAO_Contact::buildOptions('contact_type'),
5175 'is_fields' => TRUE,
5176 'is_filters' => TRUE,
5177 'is_group_bys' => TRUE,
5178 ),
5179 $options['prefix'] . 'contact_sub_type' => array(
c576cb65 5180 'title' => $options['prefix_label'] . ts('Contact Sub Type'),
d7e34fc6 5181 'name' => 'contact_sub_type',
5182 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
5183 'options' => CRM_Contact_BAO_Contact::buildOptions('contact_sub_type'),
5184 'is_fields' => TRUE,
5185 'is_filters' => TRUE,
5186 'is_group_bys' => TRUE,
5187 ),
5188 $options['prefix'] . 'is_deleted' => array(
c576cb65 5189 'title' => $options['prefix_label'] . ts('Is deleted'),
d7e34fc6 5190 'name' => 'is_deleted',
5191 'type' => CRM_Utils_Type::T_BOOLEAN,
5192 'is_fields' => FALSE,
5193 'is_filters' => TRUE,
5194 'is_group_bys' => FALSE,
5195 ),
5196 );
5197 $individualFields = array(
5198 $options['prefix'] . 'first_name' => array(
5199 'name' => 'first_name',
c576cb65 5200 'title' => $options['prefix_label'] . ts('First Name'),
d7e34fc6 5201 'is_fields' => TRUE,
5202 'is_filters' => TRUE,
5203 'is_order_bys' => TRUE,
5204 ),
5205 $options['prefix'] . 'middle_name' => array(
5206 'name' => 'middle_name',
c576cb65 5207 'title' => $options['prefix_label'] . ts('Middle Name'),
d7e34fc6 5208 'is_fields' => TRUE,
5209 ),
5210 $options['prefix'] . 'last_name' => array(
5211 'name' => 'last_name',
c576cb65 5212 'title' => $options['prefix_label'] . ts('Last Name'),
d7e34fc6 5213 'default_order' => 'ASC',
5214 'is_fields' => TRUE,
5215 ),
5216 $options['prefix'] . 'nick_name' => array(
5217 'name' => 'nick_name',
c576cb65 5218 'title' => $options['prefix_label'] . ts('Nick Name'),
d7e34fc6 5219 'is_fields' => TRUE,
5220 ),
5221 $options['prefix'] . 'gender_id' => array(
5222 'name' => 'gender_id',
c576cb65 5223 'title' => $options['prefix_label'] . ts('Gender'),
d7e34fc6 5224 'options' => CRM_Contact_BAO_Contact::buildOptions('gender_id'),
5225 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
5226 'alter_display' => 'alterGenderID',
5227 'is_fields' => TRUE,
5228 'is_filters' => TRUE,
5229 ),
5230 'birth_date' => array(
c576cb65 5231 'title' => $options['prefix_label'] . ts('Birth Date'),
d7e34fc6 5232 'operatorType' => CRM_Report_Form::OP_DATE,
5233 'type' => CRM_Utils_Type::T_DATE,
5234 'is_fields' => TRUE,
5235 'is_filters' => TRUE,
5236 ),
5237 'age' => array(
c576cb65 5238 'title' => $options['prefix_label'] . ts('Age'),
d7e34fc6 5239 'dbAlias' => 'TIMESTAMPDIFF(YEAR, ' . $tableAlias . '.birth_date, CURDATE())',
5240 'type' => CRM_Utils_Type::T_INT,
5241 'is_fields' => TRUE,
5242 ),
5243 $options['prefix'] . 'is_deceased' => array(
c576cb65 5244 'title' => $options['prefix_label'] . ts('Is deceased'),
d7e34fc6 5245 'name' => 'is_deceased',
5246 'type' => CRM_Utils_Type::T_BOOLEAN,
5247 'is_fields' => FALSE,
5248 'is_filters' => TRUE,
5249 'is_group_bys' => FALSE,
5250 ),
5251 );
5252 if (!$options['contact_type'] || $options['contact_type'] === 'Individual') {
5253 $spec = array_merge($spec, $individualFields);
5254 }
5255
5256 if (!empty($options['custom_fields'])) {
5257 $this->_customGroupExtended[$options['prefix'] . 'civicrm_contact'] = array(
5258 'extends' => $options['custom_fields'],
5259 'title' => $options['prefix_label'],
5260 'filters' => $options['filters'],
5261 'prefix' => $options['prefix'],
5262 'prefix_label' => $options['prefix_label'],
5263 );
5264 }
5265
5266 return $this->buildColumns($spec, $options['prefix'] . 'civicrm_contact', 'CRM_Contact_DAO_Contact', $tableAlias, $this->getDefaultsFromOptions($options), $options);
5267 }
5268
5269 /**
5270 * Build the columns.
5271 *
5272 * The normal report class needs you to remember to do a few things that are often erratic
5273 *
5274 * 1) use a unique key for any field that might not be unique (e.g. start date, label)
5275 * - this class will prepend an alias to the key & set the 'name' if you don't set it yourself.
5276 * You can suppress the alias with 'no_field_disambiguation' if transitioning existing reports. This
5277 * means any saved filters / fields on saved report instances. This will mean that matching names from
5278 * different tables may be ambigious, but it will smooth any code transition.
5279 * - note that it assumes the value being passed in is the actual table field name
5280 *
5281 * 2) set the field & set it to no display if you don't want the field but you might want to use the field in other
5282 * contexts - the code looks up the fields array for data - so it both defines the field spec & the fields you want to show
5283 *
5284 * 3) this function also sets the 'metadata' array - the extended report class now uses this in place
5285 * of the fields array to reduce the issues caused when metadata is needed but 'fields' are not defined. Code in
5286 * the core classes can start to move towards that.
5287 *
5288 * @param array $specs
5289 * @param string $tableName
5290 * @param string $daoName
5291 * @param string $tableAlias
5292 * @param array $defaults
5293 * @param array $options
5294 *
5295 * @return array
5296 */
5297 protected function buildColumns($specs, $tableName, $daoName = NULL, $tableAlias = NULL, $defaults = array(), $options = array()) {
5298 if (!$tableAlias) {
5299 $tableAlias = str_replace('civicrm_', '', $tableName);
5300 }
5301 $types = array('filters', 'group_bys', 'order_bys', 'join_filters');
5302 $columns = array($tableName => array_fill_keys($types, array()));
5303 // The code that uses this no longer cares if it is a DAO or BAO so just call it a DAO.
5304 $columns[$tableName]['dao'] = $daoName;
5305 $columns[$tableName]['alias'] = $tableAlias;
5306
5307 foreach ($specs as $specName => $spec) {
5308 if (empty($spec['name'])) {
5309 $spec['name'] = $specName;
5310 }
5311
5312 $fieldAlias = (empty($options['no_field_disambiguation']) ? $tableAlias . '_' : '') . $specName;
5313 $columns[$tableName]['metadata'][$fieldAlias] = $spec;
5314 $columns[$tableName]['fields'][$fieldAlias] = $spec;
5315 if (isset($defaults['fields_defaults']) && in_array($spec['name'], $defaults['fields_defaults'])) {
5316 $columns[$tableName]['fields'][$fieldAlias]['default'] = TRUE;
5317 }
5318
5319 if (!$spec['is_fields'] || (isset($options['fields_excluded']) && in_array($specName, $options['fields_excluded']))) {
5320 $columns[$tableName]['fields'][$fieldAlias]['no_display'] = TRUE;
5321 }
5322
5323 if (isset($options['fields_required']) && in_array($specName, $options['fields_required'])) {
5324 $columns[$tableName]['fields'][$fieldAlias]['required'] = TRUE;
5325 }
5326
5327 foreach ($types as $type) {
5328 if ($options[$type] && !empty($spec['is_' . $type])) {
5329 $columns[$tableName][$type][$fieldAlias] = $spec;
5330 if (isset($defaults[$type . '_defaults']) && isset($defaults[$type . '_defaults'][$spec['name']])) {
5331 $columns[$tableName][$type][$fieldAlias]['default'] = $defaults[$type . '_defaults'][$spec['name']];
5332 }
5333 }
5334 }
5335 }
5336 return $columns;
5337 }
5338
534c4d20 5339 /**
5340 * Store group bys into array - so we can check elsewhere what is grouped.
5341 */
5342 protected function storeGroupByArray() {
5343
c3fdd2b7 5344 if (!CRM_Utils_Array::value('group_bys', $this->_params)
5345 || !is_array($this->_params['group_bys'])) {
5346 $this->_params['group_bys'] = [];
5347 }
534c4d20 5348
c3fdd2b7 5349 foreach ($this->_columns as $tableName => $table) {
5350 $table = $this->_columns[$tableName];
5351 if (array_key_exists('group_bys', $table)) {
5352 foreach ($table['group_bys'] as $fieldName => $fieldData) {
5353 $field = $this->_columns[$tableName]['metadata'][$fieldName];
5354 if (!empty($this->_params['group_bys'][$fieldName]) || !empty($fieldData['required'])) {
5355 if (!empty($field['chart'])) {
5356 $this->assign('chartSupported', TRUE);
5357 }
534c4d20 5358
c3fdd2b7 5359 if (!empty($table['group_bys'][$fieldName]['frequency']) &&
5360 !empty($this->_params['group_bys_freq'][$fieldName])
5361 ) {
534c4d20 5362
c3fdd2b7 5363 switch ($this->_params['group_bys_freq'][$fieldName]) {
5364 case 'FISCALYEAR':
5365 $this->_groupByArray[$tableName . '_' . $fieldName . '_start'] = self::fiscalYearOffset($field['dbAlias']);
534c4d20 5366
c3fdd2b7 5367 case 'YEAR':
5368 $this->_groupByArray[$tableName . '_' . $fieldName . '_start'] = " {$this->_params['group_bys_freq'][$fieldName]}({$field['dbAlias']})";
5369
5370 default:
5371 $this->_groupByArray[$tableName . '_' . $fieldName . '_start'] = "EXTRACT(YEAR_{$this->_params['group_bys_freq'][$fieldName]} FROM {$field['dbAlias']})";
534c4d20 5372
534c4d20 5373 }
c3fdd2b7 5374 }
5375 else {
5376 if (!in_array($field['dbAlias'], $this->_groupByArray)) {
5377 $this->_groupByArray[$tableName . '_' . $fieldName] = $field['dbAlias'];
534c4d20 5378 }
5379 }
5380 }
534c4d20 5381 }
c3fdd2b7 5382
534c4d20 5383 }
5384 }
5385 }
5386
d7e34fc6 5387 /**
5388 * @param $options
5389 *
5390 * @return array
5391 */
5392 protected function getDefaultsFromOptions($options) {
5393 $defaults = array(
5394 'fields_defaults' => $options['fields_defaults'],
5395 'filters_defaults' => $options['filters_defaults'],
5396 'group_bys_defaults' => $options['group_bys_defaults'],
5397 'order_bys_defaults' => $options['order_bys_defaults'],
5398 );
5399 return $defaults;
5400 }
5401
81a22d3d 5402 /**
5403 * Get the select clause for a field, wrapping in GROUP_CONCAT if appropriate.
5404 *
5405 * Full group by mode dictates that a field must either be in the group by function or
5406 * wrapped in a aggregate function. Here we wrap the field in GROUP_CONCAT if it is not in the
5407 * group concat.
5408 *
5409 * @param string $tableName
5410 * @param string $fieldName
5411 * @param string $field
5412 * @return string
5413 */
5414 protected function getSelectClauseWithGroupConcatIfNotGroupedBy($tableName, &$fieldName, &$field) {
5415 if ($this->groupConcatTested && (!empty($this->_groupByArray) || $this->isForceGroupBy)) {
5416 if ((empty($field['statistics']) || in_array('GROUP_CONCAT', $field['statistics']))) {
5417 $label = CRM_Utils_Array::value('title', $field);
5418 $alias = "{$tableName}_{$fieldName}";
5419 $this->_columnHeaders["{$tableName}_{$fieldName}"]['title'] = $label;
5420 $this->_selectAliases[] = $alias;
5421 if (empty($this->_groupByArray[$tableName . '_' . $fieldName])) {
5422 return "GROUP_CONCAT(DISTINCT {$field['dbAlias']}) as $alias";
5423 }
5424 return "({$field['dbAlias']}) as $alias";
5425 }
5426 }
5427 }
5428
232624b1 5429}