Merge pull request #23076 from eileenmcnaughton/cont_status
[civicrm-core.git] / CRM / Report / Form / Activity.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 class CRM_Report_Form_Activity extends CRM_Report_Form {
18 protected $_selectAliasesTotal = [];
19
20 protected $_customGroupExtends = ['Activity'];
21
22 protected $_nonDisplayFields = [];
23
24 /**
25 * This report has not been optimised for group filtering.
26 *
27 * The functionality for group filtering has been improved but not
28 * all reports have been adjusted to take care of it. This report has not
29 * and will run an inefficient query until fixed.
30 *
31 * @var bool
32 * @see https://issues.civicrm.org/jira/browse/CRM-19170
33 */
34 protected $groupFilterNotOptimised = TRUE;
35
36 /**
37 * Class constructor.
38 *
39 * @throws \CRM_Core_Exception
40 */
41 public function __construct() {
42 // There could be multiple contacts. We not clear on which contact id to display.
43 // Lets hide it for now.
44 $this->_exposeContactID = FALSE;
45 // if navigated from count link of activity summary reports.
46 $this->_resetDateFilter = CRM_Utils_Request::retrieve('resetDateFilter', 'Boolean');
47
48 $components = CRM_Core_Component::getEnabledComponents();
49 $campaignEnabled = !empty($components['CiviCampaign']);
50 $caseEnabled = !empty($components['CiviCase']);
51
52 foreach ($components as $componentName => $componentInfo) {
53 // CRM-19201: Add support for reporting CiviCampaign activities
54 // For CiviCase, "access all cases and activities" is required here
55 // rather than "access my cases and activities" to prevent those with
56 // only the later permission from seeing a list of all cases which might
57 // present a privacy issue.
58 if (CRM_Core_Permission::access($componentName, TRUE, TRUE)) {
59 $accessAllowed[] = $componentInfo->componentID;
60 }
61 }
62
63 $include = '';
64 if (!empty($accessAllowed)) {
65 $include = 'OR v.component_id IN (' . implode(', ', $accessAllowed) . ')';
66 }
67 $condition = " AND ( v.component_id IS NULL {$include} )";
68 $this->activityTypes = CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, $condition);
69 asort($this->activityTypes);
70
71 // @todo split the 3 different contact tables into their own array items.
72 // this will massively simplify the needs of this report.
73 $this->_columns = [
74 'civicrm_contact' => [
75 'dao' => 'CRM_Contact_DAO_Contact',
76 'fields' => [
77 'contact_source' => [
78 'name' => 'sort_name',
79 'title' => ts('Source Name'),
80 'alias' => 'civicrm_contact_source',
81 'no_repeat' => TRUE,
82 ],
83 'contact_assignee' => [
84 'name' => 'sort_name',
85 'title' => ts('Assignee Name'),
86 'alias' => 'civicrm_contact_assignee',
87 'dbAlias' => "civicrm_contact_assignee.sort_name",
88 'default' => TRUE,
89 ],
90 'contact_target' => [
91 'name' => 'sort_name',
92 'title' => ts('Target Name'),
93 'alias' => 'civicrm_contact_target',
94 'dbAlias' => "civicrm_contact_target.sort_name",
95 'default' => TRUE,
96 ],
97 'contact_target_birth' => [
98 'name' => 'birth_date',
99 'title' => ts('Target Birth Date'),
100 'alias' => 'civicrm_contact_target',
101 'dbAlias' => "civicrm_contact_target.birth_date",
102 ],
103 'contact_target_gender' => [
104 'name' => 'gender_id',
105 'title' => ts('Target Gender'),
106 'alias' => 'civicrm_contact_target',
107 'dbAlias' => "civicrm_contact_target.gender_id",
108 'default' => TRUE,
109 ],
110 'contact_source_id' => [
111 'name' => 'id',
112 'alias' => 'civicrm_contact_source',
113 'dbAlias' => "civicrm_contact_source.id",
114 'no_display' => TRUE,
115 'default' => TRUE,
116 'required' => TRUE,
117 ],
118 'contact_assignee_id' => [
119 'name' => 'id',
120 'alias' => 'civicrm_contact_assignee',
121 'dbAlias' => "civicrm_contact_assignee.id",
122 'no_display' => TRUE,
123 'default' => TRUE,
124 'required' => TRUE,
125 ],
126 'contact_target_id' => [
127 'name' => 'id',
128 'alias' => 'civicrm_contact_target',
129 'dbAlias' => "civicrm_contact_target.id",
130 'no_display' => TRUE,
131 'default' => TRUE,
132 'required' => TRUE,
133 ],
134 ],
135 'filters' => [
136 'contact_source' => [
137 'name' => 'sort_name',
138 'alias' => 'civicrm_contact_source',
139 'title' => ts('Source Name'),
140 'operator' => 'like',
141 'type' => CRM_Report_Form::OP_STRING,
142 ],
143 'contact_assignee' => [
144 'name' => 'sort_name',
145 'alias' => 'civicrm_contact_assignee',
146 'title' => ts('Assignee Name'),
147 'operator' => 'like',
148 'type' => CRM_Report_Form::OP_STRING,
149 ],
150 'contact_target' => [
151 'name' => 'sort_name',
152 'alias' => 'civicrm_contact_target',
153 'title' => ts('Target Name'),
154 'operator' => 'like',
155 'type' => CRM_Report_Form::OP_STRING,
156 ],
157 'current_user' => [
158 'name' => 'current_user',
159 'title' => ts('Limit To Current User'),
160 'type' => CRM_Utils_Type::T_INT,
161 'operatorType' => CRM_Report_Form::OP_SELECT,
162 'options' => ['0' => ts('No'), '1' => ts('Yes')],
163 ],
164 ],
165 'grouping' => 'contact-fields',
166 ],
167 'civicrm_email' => [
168 'dao' => 'CRM_Core_DAO_Email',
169 'fields' => [
170 'contact_source_email' => [
171 'name' => 'email',
172 'title' => ts('Source Email'),
173 'alias' => 'civicrm_email_source',
174 ],
175 'contact_assignee_email' => [
176 'name' => 'email',
177 'title' => ts('Assignee Email'),
178 'alias' => 'civicrm_email_assignee',
179 ],
180 'contact_target_email' => [
181 'name' => 'email',
182 'title' => ts('Target Email'),
183 'alias' => 'civicrm_email_target',
184 ],
185 ],
186 'order_bys' => [
187 'source_contact_email' => [
188 'name' => 'email',
189 'title' => ts('Source Email'),
190 'dbAlias' => 'civicrm_email_contact_source_email',
191 ],
192 ],
193 ],
194 'civicrm_phone' => [
195 'dao' => 'CRM_Core_DAO_Phone',
196 'fields' => [
197 'contact_source_phone' => [
198 'name' => 'phone',
199 'title' => ts('Source Phone'),
200 'alias' => 'civicrm_phone_source',
201 ],
202 'contact_assignee_phone' => [
203 'name' => 'phone',
204 'title' => ts('Assignee Phone'),
205 'alias' => 'civicrm_phone_assignee',
206 ],
207 'contact_target_phone' => [
208 'name' => 'phone',
209 'title' => ts('Target Phone'),
210 'alias' => 'civicrm_phone_target',
211 ],
212 ],
213 ],
214 'civicrm_activity' => [
215 'dao' => 'CRM_Activity_DAO_Activity',
216 'fields' => [
217 'id' => [
218 'no_display' => TRUE,
219 'title' => ts('Activity ID'),
220 'required' => TRUE,
221 ],
222 'source_record_id' => [
223 'no_display' => TRUE,
224 'required' => TRUE,
225 ],
226 'activity_type_id' => [
227 'title' => ts('Activity Type'),
228 'required' => TRUE,
229 'type' => CRM_Utils_Type::T_STRING,
230 ],
231 'activity_subject' => [
232 'title' => ts('Subject'),
233 'default' => TRUE,
234 ],
235 'activity_date_time' => [
236 'title' => ts('Activity Date'),
237 'required' => TRUE,
238 ],
239 'status_id' => [
240 'title' => ts('Activity Status'),
241 'default' => TRUE,
242 'type' => CRM_Utils_Type::T_STRING,
243 ],
244 'duration' => [
245 'title' => ts('Duration'),
246 'type' => CRM_Utils_Type::T_INT,
247 ],
248 'location' => [
249 'title' => ts('Location'),
250 'type' => CRM_Utils_Type::T_STRING,
251 ],
252 'details' => [
253 'title' => ts('Activity Details'),
254 ],
255 'priority_id' => [
256 'title' => ts('Priority'),
257 'default' => TRUE,
258 'type' => CRM_Utils_Type::T_STRING,
259 ],
260 ],
261 'filters' => [
262 'activity_date_time' => [
263 'default' => 'this.month',
264 'operatorType' => CRM_Report_Form::OP_DATE,
265 ],
266 'activity_subject' => ['title' => ts('Activity Subject')],
267 'activity_type_id' => [
268 'title' => ts('Activity Type'),
269 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
270 'options' => $this->activityTypes,
271 ],
272 'status_id' => [
273 'title' => ts('Activity Status'),
274 'type' => CRM_Utils_Type::T_STRING,
275 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
276 'options' => CRM_Core_PseudoConstant::activityStatus(),
277 ],
278 'location' => [
279 'title' => ts('Location'),
280 'type' => CRM_Utils_Type::T_TEXT,
281 ],
282 'details' => [
283 'title' => ts('Activity Details'),
284 'type' => CRM_Utils_Type::T_TEXT,
285 ],
286 'priority_id' => [
287 'title' => ts('Activity Priority'),
288 'type' => CRM_Utils_Type::T_STRING,
289 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
290 'options' => CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'priority_id'),
291 ],
292 ],
293 'order_bys' => [
294 'activity_date_time' => [
295 'title' => ts('Activity Date'),
296 'default_weight' => '1',
297 'dbAlias' => 'civicrm_activity_activity_date_time',
298 ],
299 'activity_type_id' => [
300 'title' => ts('Activity Type'),
301 'default_weight' => '2',
302 'dbAlias' => 'field(civicrm_activity_activity_type_id, ' . implode(', ', array_keys($this->activityTypes)) . ')',
303 ],
304 ],
305 'grouping' => 'activity-fields',
306 'alias' => 'activity',
307 ],
308 // Hack to get $this->_alias populated for the table.
309 'civicrm_activity_contact' => [
310 'dao' => 'CRM_Activity_DAO_ActivityContact',
311 'fields' => [],
312 ],
313 ] + $this->addressFields(TRUE);
314
315 if ($caseEnabled && CRM_Core_Permission::check('access all cases and activities')) {
316 $this->_columns['civicrm_activity']['filters']['include_case_activities'] = [
317 'name' => 'include_case_activities',
318 'title' => ts('Include Case Activities'),
319 'type' => CRM_Utils_Type::T_INT,
320 'operatorType' => CRM_Report_Form::OP_SELECT,
321 'options' => ['0' => ts('No'), '1' => ts('Yes')],
322 ];
323 $this->_columns['civicrm_case_activity'] = [
324 'dao' => 'CRM_Case_DAO_CaseActivity',
325 'fields' => [],
326 ];
327 }
328
329 if ($campaignEnabled) {
330 // Add display column and filter for Survey Results, Campaign and Engagement Index if CiviCampaign is enabled
331
332 $this->_columns['civicrm_activity']['fields']['result'] = [
333 'title' => ts('Survey Result'),
334 'default' => 'false',
335 ];
336 $this->_columns['civicrm_activity']['filters']['result'] = [
337 'title' => ts('Survey Result'),
338 'operator' => 'like',
339 'type' => CRM_Utils_Type::T_STRING,
340 ];
341 // If we have campaigns enabled, add those elements to both the fields, filters.
342 $this->addCampaignFields('civicrm_activity');
343 $this->engagementLevels = $campaignEnabled ? CRM_Campaign_PseudoConstant::engagementLevel() : [];
344 if (!empty($this->engagementLevels)) {
345 $this->_columns['civicrm_activity']['fields']['engagement_level'] = [
346 'title' => ts('Engagement Index'),
347 'default' => 'false',
348 ];
349 $this->_columns['civicrm_activity']['filters']['engagement_level'] = [
350 'title' => ts('Engagement Index'),
351 'type' => CRM_Utils_Type::T_INT,
352 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
353 'options' => $this->engagementLevels,
354 ];
355 }
356 }
357 $this->_groupFilter = TRUE;
358 $this->_tagFilter = TRUE;
359 $this->_tagFilterTable = 'civicrm_activity';
360
361 parent::__construct();
362 }
363
364 protected static function addCaseActivityColumns($columns) {
365 $columns['civicrm_case_activity']['fields'] = [
366 'case_id' => [
367 'title' => ts('Case ID'),
368 'required' => TRUE,
369 'dbAlias' => $columns['civicrm_case_activity']['alias'] . '.case_id',
370 'type' => CRM_Utils_Type::T_INT,
371 ],
372 ];
373 return $columns;
374 }
375
376 /**
377 * Adding address fields with dbAlias for order clause.
378 *
379 * @param bool $orderBy
380 *
381 * @return array
382 * Address fields
383 */
384 public function addressFields($orderBy = FALSE) {
385 $address = parent::addAddressFields(FALSE, TRUE);
386 if ($orderBy) {
387 foreach ($address['civicrm_address']['order_bys'] as $fieldName => $field) {
388 $address['civicrm_address']['order_bys'][$fieldName]['dbAlias'] = "civicrm_address_{$fieldName}";
389 }
390 }
391 return $address;
392 }
393
394 /**
395 * Build select clause.
396 *
397 * @todo get rid of $recordType param. It's only because 3 separate contact tables
398 * are mis-declared as one that we need it.
399 *
400 * @param string $recordType deprecated
401 * Parameter to hack around the bad decision made in construct to misrepresent
402 * different tables as the same table.
403 */
404 public function select($recordType = 'target') {
405 if (!array_key_exists("contact_{$recordType}", $this->_params['fields']) &&
406 $recordType != 'final'
407 ) {
408 $this->_nonDisplayFields[] = "civicrm_contact_contact_{$recordType}";
409 }
410 parent::select();
411
412 if ($recordType == 'final' && !empty($this->_nonDisplayFields)) {
413 foreach ($this->_nonDisplayFields as $fieldName) {
414 unset($this->_columnHeaders[$fieldName]);
415 }
416 }
417
418 if (empty($this->_selectAliasesTotal)) {
419 $this->_selectAliasesTotal = $this->_selectAliases;
420 }
421
422 $removeKeys = [];
423 if ($recordType == 'target') {
424 // @todo - fix up the way the tables are declared in construct & remove this.
425 foreach ($this->_selectClauses as $key => $clause) {
426 if (strstr($clause, 'civicrm_contact_assignee.') ||
427 strstr($clause, 'civicrm_contact_source.') ||
428 strstr($clause, 'civicrm_email_assignee.') ||
429 strstr($clause, 'civicrm_email_source.') ||
430 strstr($clause, 'civicrm_phone_assignee.') ||
431 strstr($clause, 'civicrm_phone_source.')
432 ) {
433 $removeKeys[] = $key;
434 unset($this->_selectClauses[$key]);
435 }
436 }
437 }
438 elseif ($recordType == 'assignee') {
439 // @todo - fix up the way the tables are declared in construct & remove this.
440 foreach ($this->_selectClauses as $key => $clause) {
441 if (strstr($clause, 'civicrm_contact_target.') ||
442 strstr($clause, 'civicrm_contact_source.') ||
443 strstr($clause, 'civicrm_email_target.') ||
444 strstr($clause, 'civicrm_email_source.') ||
445 strstr($clause, 'civicrm_phone_target.') ||
446 strstr($clause, 'civicrm_phone_source.') ||
447 strstr($clause, 'civicrm_address_')
448 ) {
449 $removeKeys[] = $key;
450 unset($this->_selectClauses[$key]);
451 }
452 }
453 }
454 elseif ($recordType == 'source') {
455 // @todo - fix up the way the tables are declared in construct & remove this.
456 foreach ($this->_selectClauses as $key => $clause) {
457 if (strstr($clause, 'civicrm_contact_target.') ||
458 strstr($clause, 'civicrm_contact_assignee.') ||
459 strstr($clause, 'civicrm_email_target.') ||
460 strstr($clause, 'civicrm_email_assignee.') ||
461 strstr($clause, 'civicrm_phone_target.') ||
462 strstr($clause, 'civicrm_phone_assignee.') ||
463 strstr($clause, 'civicrm_address_')
464 ) {
465 $removeKeys[] = $key;
466 unset($this->_selectClauses[$key]);
467 }
468 }
469 }
470 elseif ($recordType == 'final') {
471 $this->_selectClauses = $this->_selectAliasesTotal;
472 foreach ($this->_selectClauses as $key => $clause) {
473 // @todo - fix up the way the tables are declared in construct & remove this.
474 if (strstr($clause, 'civicrm_contact_contact_target') ||
475 strstr($clause, 'civicrm_contact_contact_assignee') ||
476 strstr($clause, 'civicrm_contact_contact_source') ||
477 strstr($clause, 'civicrm_phone_contact_source_phone') ||
478 strstr($clause, 'civicrm_phone_contact_assignee_phone') ||
479 strstr($clause, 'civicrm_email_contact_source_email') ||
480 strstr($clause, 'civicrm_email_contact_assignee_email') ||
481 strstr($clause, 'civicrm_email_contact_target_email') ||
482 strstr($clause, 'civicrm_phone_contact_target_phone') ||
483 strstr($clause, 'civicrm_address_')
484 ) {
485 $this->_selectClauses[$key] = "GROUP_CONCAT(DISTINCT $clause SEPARATOR ';') as $clause";
486 }
487 }
488 }
489
490 if ($recordType) {
491 foreach ($removeKeys as $key) {
492 unset($this->_selectAliases[$key]);
493 }
494
495 if ($recordType == 'target') {
496 foreach ($this->_columns['civicrm_address']['order_bys'] as $fieldName => $field) {
497 $orderByFld = $this->_columns['civicrm_address']['order_bys'][$fieldName];
498 $fldInfo = $this->_columns['civicrm_address']['fields'][$fieldName];
499 $this->_selectAliases[] = $orderByFld['dbAlias'];
500 $this->_selectClauses[] = "{$fldInfo['dbAlias']} as {$orderByFld['dbAlias']}";
501 }
502 $this->_selectAliases = array_unique($this->_selectAliases);
503 $this->_selectClauses = array_unique($this->_selectClauses);
504 }
505 $this->_select = "SELECT " . implode(', ', $this->_selectClauses) . " ";
506 }
507 }
508
509 /**
510 * Build from clause.
511 * @todo remove this function & declare the 3 contact tables separately
512 */
513 public function from() {
514 $this->buildFrom('target');
515 }
516
517 /**
518 * Build where clause.
519 *
520 * @todo get rid of $recordType param. It's only because 3 separate contact tables
521 * are mis-declared as one that we need it.
522 *
523 * @param string $recordType
524 */
525 public function where($recordType = NULL) {
526 $this->_where = " WHERE {$this->_aliases['civicrm_activity']}.is_test = 0 AND
527 {$this->_aliases['civicrm_activity']}.is_deleted = 0 AND
528 {$this->_aliases['civicrm_activity']}.is_current_revision = 1";
529
530 $clauses = [];
531 foreach ($this->_columns as $tableName => $table) {
532 if (array_key_exists('filters', $table)) {
533
534 foreach ($table['filters'] as $fieldName => $field) {
535 $clause = NULL;
536 if ($fieldName != 'contact_' . $recordType &&
537 (strstr($fieldName, '_target') ||
538 strstr($fieldName, '_assignee') ||
539 strstr($fieldName, '_source')
540 )
541 ) {
542 continue;
543 }
544 if (CRM_Utils_Array::value('type', $field) & CRM_Utils_Type::T_DATE) {
545 $relative = $this->_params["{$fieldName}_relative"] ?? NULL;
546 $from = $this->_params["{$fieldName}_from"] ?? NULL;
547 $to = $this->_params["{$fieldName}_to"] ?? NULL;
548
549 $clause = $this->dateClause($field['dbAlias'], $relative, $from, $to, $field['type']);
550 }
551 else {
552 $op = $this->_params["{$fieldName}_op"] ?? NULL;
553 if ($op && !($fieldName === "contact_{$recordType}" && ($op === 'nnll' || $op === 'nll'))) {
554 $clause = $this->whereClause($field,
555 $op,
556 CRM_Utils_Array::value("{$fieldName}_value", $this->_params),
557 CRM_Utils_Array::value("{$fieldName}_min", $this->_params),
558 CRM_Utils_Array::value("{$fieldName}_max", $this->_params)
559 );
560 if ($field['name'] == 'include_case_activities') {
561 $clause = NULL;
562 }
563 if ($fieldName == 'activity_type_id' &&
564 empty($this->_params['activity_type_id_value'])
565 ) {
566 if (empty($this->_params['include_case_activities_value'])) {
567 $this->activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'label', TRUE);
568 }
569 $actTypes = array_flip($this->activityTypes);
570 $clause = "( {$this->_aliases['civicrm_activity']}.activity_type_id IN (" .
571 implode(',', $actTypes) . ") )";
572 }
573 }
574 }
575
576 if ($field['name'] == 'current_user') {
577 if (CRM_Utils_Array::value("{$fieldName}_value", $this->_params) ==
578 1
579 ) {
580 // get current user
581 if ($contactID = CRM_Core_Session::getLoggedInContactID()) {
582 $clause = "{$this->_aliases['civicrm_activity_contact']}.activity_id IN
583 (SELECT activity_id FROM civicrm_activity_contact WHERE contact_id = {$contactID})";
584 }
585 else {
586 $clause = NULL;
587 }
588 }
589 else {
590 $clause = NULL;
591 }
592 }
593 if (!empty($clause)) {
594 $clauses[] = $clause;
595 }
596 }
597 }
598 }
599
600 if (empty($clauses)) {
601 $this->_where .= " ";
602 }
603 else {
604 $this->_where .= " AND " . implode(' AND ', $clauses);
605 }
606
607 if ($this->_aclWhere) {
608 $this->_where .= " AND {$this->_aclWhere} ";
609 }
610 }
611
612 /**
613 * Override group by function.
614 */
615 public function groupBy() {
616 $this->_groupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($this->_selectClauses, "{$this->_aliases['civicrm_activity']}.id");
617 }
618
619 /**
620 * Build ACL clause.
621 *
622 * @param array $tableAlias
623 *
624 * @throws \CRM_Core_Exception
625 */
626 public function buildACLClause($tableAlias = 'contact_a') {
627 //override for ACL( Since Contact may be source
628 //contact/assignee or target also it may be null )
629
630 if (CRM_Core_Permission::check('view all contacts')) {
631 $this->_aclFrom = $this->_aclWhere = NULL;
632 return;
633 }
634
635 $contactID = CRM_Core_Session::getLoggedInContactID();
636 if (!$contactID) {
637 $contactID = 0;
638 }
639 $contactID = CRM_Utils_Type::escape($contactID, 'Integer');
640
641 CRM_Contact_BAO_Contact_Permission::cache($contactID);
642 $clauses = [];
643 foreach ($tableAlias as $k => $alias) {
644 $clauses[] = " INNER JOIN civicrm_acl_contact_cache aclContactCache_{$k} ON ( {$alias}.id = aclContactCache_{$k}.contact_id OR {$alias}.id IS NULL ) AND aclContactCache_{$k}.user_id = $contactID ";
645 }
646
647 $this->_aclFrom = implode(" ", $clauses);
648 $this->_aclWhere = NULL;
649 }
650
651 /**
652 * @param int $groupID
653 *
654 * @throws Exception
655 */
656 public function add2group($groupID) {
657 if (CRM_Utils_Array::value("contact_target_op", $this->_params) == 'nll') {
658 CRM_Core_Error::statusBounce(ts('Current filter criteria didn\'t have any target contact to add to group'));
659 }
660
661 $new_select = 'AS addtogroup_contact_id';
662 $select = str_ireplace('AS civicrm_contact_contact_target_id', $new_select, $this->_select);
663 $new_having = ' addtogroup_contact_id';
664 $having = str_ireplace(' civicrm_contact_contact_target_id', $new_having, $this->_having);
665 $query = "$select
666 FROM {$this->temporaryTables['activity_temp_table']['name']} tar
667 GROUP BY civicrm_activity_id $having {$this->_orderBy}";
668 $select = 'AS addtogroup_contact_id';
669 $query = str_ireplace('AS civicrm_contact_contact_target_id', $select, $query);
670 CRM_Core_DAO::disableFullGroupByMode();
671 $dao = $this->executeReportQuery($query);
672 CRM_Core_DAO::reenableFullGroupByMode();
673
674 $contactIDs = [];
675 // Add resulting contacts to group
676 while ($dao->fetch()) {
677 if ($dao->addtogroup_contact_id) {
678 $contact_id = explode(';', $dao->addtogroup_contact_id);
679 if ($contact_id[0]) {
680 $contactIDs[$contact_id[0]] = $contact_id[0];
681 }
682 }
683 }
684
685 if (!empty($contactIDs)) {
686 CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIDs, $groupID);
687 CRM_Core_Session::setStatus(ts("Listed contact(s) have been added to the selected group."), ts('Contacts Added'), 'success');
688 }
689 else {
690 CRM_Core_Session::setStatus(ts("The listed records(s) cannot be added to the group."));
691 }
692 }
693
694 /**
695 * @param $fields
696 * @param $files
697 * @param self $self
698 *
699 * @return array
700 */
701 public static function formRule($fields, $files, $self) {
702 $errors = [];
703 if (CRM_Core_Component::isEnabled('CiviCase')) {
704 $componentId = CRM_Core_Component::getComponentID('CiviCase');
705 $caseActivityTypes = CRM_Core_OptionGroup::values('activity_type', TRUE, FALSE, FALSE, " AND v.component_id={$componentId}");
706 if (!empty($fields['activity_type_id_value']) && is_array($fields['activity_type_id_value']) && empty($fields['include_case_activities_value'])) {
707 foreach ($fields['activity_type_id_value'] as $activityTypeId) {
708 if (in_array($activityTypeId, $caseActivityTypes)) {
709 $errors['fields'] = ts("Please enable 'Include Case Activities' to filter with Case Activity types.");
710 }
711 }
712 }
713 }
714 return $errors;
715 }
716
717 /**
718 * @param $applyLimit
719 *
720 * @return string
721 */
722 public function buildQuery($applyLimit = TRUE) {
723 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
724 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
725
726 //Assign those recordtype to array which have filter operator as 'Is not empty' or 'Is empty'
727 $nullFilters = [];
728 foreach (['target', 'source', 'assignee'] as $type) {
729 if (CRM_Utils_Array::value("contact_{$type}_op", $this->_params) ==
730 'nnll' || !empty($this->_params["contact_{$type}_value"])
731 ) {
732 $nullFilters[] = " civicrm_contact_contact_{$type}_id IS NOT NULL ";
733 }
734 elseif (CRM_Utils_Array::value("contact_{$type}_op", $this->_params) ==
735 'nll'
736 ) {
737 $nullFilters[] = " civicrm_contact_contact_{$type}_id IS NULL ";
738 }
739 }
740
741 if (!empty($this->_params['include_case_activities_value'])) {
742 $this->_columns = self::addCaseActivityColumns($this->_columns);
743 }
744
745 // @todo - all this temp table stuff is here because pre 4.4 the activity contact
746 // form did not exist.
747 // Fixing the way the construct method declares them will make all this redundant.
748 // 1. fill temp table with target results
749 $this->buildACLClause(['civicrm_contact_target']);
750 $this->select('target');
751 $this->from();
752 $this->customDataFrom();
753 $this->where('target');
754 $tempTableName = $this->createTemporaryTable('activity_temp_table', "{$this->_select} {$this->_from} {$this->_where}");
755
756 // 2. add new columns to hold assignee and source results
757 // fixme: add when required
758 $tempQuery = "
759 ALTER TABLE $tempTableName
760 MODIFY COLUMN civicrm_contact_contact_target_id VARCHAR(128),
761 ADD COLUMN civicrm_contact_contact_assignee VARCHAR(128),
762 ADD COLUMN civicrm_contact_contact_source VARCHAR(128),
763 ADD COLUMN civicrm_contact_contact_assignee_id VARCHAR(128),
764 ADD COLUMN civicrm_contact_contact_source_id VARCHAR(128),
765 ADD COLUMN civicrm_phone_contact_assignee_phone VARCHAR(128),
766 ADD COLUMN civicrm_phone_contact_source_phone VARCHAR(128),
767 ADD COLUMN civicrm_email_contact_assignee_email VARCHAR(128),
768 ADD COLUMN civicrm_email_contact_source_email VARCHAR(128)";
769 $this->executeReportQuery($tempQuery);
770
771 // 3. fill temp table with assignee results
772 $this->buildACLClause(['civicrm_contact_assignee']);
773 $this->select('assignee');
774 $this->buildAssigneeFrom();
775
776 $this->customDataFrom();
777 $this->where('assignee');
778 $insertCols = implode(',', $this->_selectAliases);
779 $tempQuery = "INSERT INTO $tempTableName ({$insertCols})
780 {$this->_select}
781 {$this->_from} {$this->_where}";
782 $this->executeReportQuery($tempQuery);
783
784 // 4. fill temp table with source results
785 $this->buildACLClause(['civicrm_contact_source']);
786 $this->select('source');
787 $this->buildSourceFrom();
788 $this->customDataFrom();
789 $this->where('source');
790 $insertCols = implode(',', $this->_selectAliases);
791 $tempQuery = "INSERT INTO $tempTableName ({$insertCols})
792 {$this->_select}
793 {$this->_from} {$this->_where}";
794 $this->executeReportQuery($tempQuery);
795
796 // 5. show final result set from temp table
797 $rows = [];
798 $this->select('final');
799 $this->_having = "";
800 if (!empty($nullFilters)) {
801 $this->_having = "HAVING " . implode(' AND ', $nullFilters);
802 }
803 $this->orderBy();
804 foreach ($this->_sections as $alias => $section) {
805 if (!empty($section) && $section['name'] == 'activity_date_time') {
806 $this->alterSectionHeaderForDateTime($tempTableName, $section['tplField']);
807 }
808 }
809
810 if ($applyLimit) {
811 $this->limit();
812 }
813
814 $groupByFromSelect = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($this->_selectClauses, 'civicrm_activity_id');
815
816 $this->_where = " WHERE (1)";
817 $this->buildPermissionClause();
818 if ($this->_aclWhere) {
819 $this->_where .= " AND {$this->_aclWhere} ";
820 }
821
822 $caseJoin = '';
823 if (!empty($this->_params['include_case_activities_value'])) {
824 $caseJoin = "LEFT JOIN civicrm_case_activity {$this->_aliases['civicrm_case_activity']} ON {$this->_aliases['civicrm_activity']}.id = {$this->_aliases['civicrm_case_activity']}.activity_id";
825 }
826
827 $sql = "{$this->_select}
828 FROM $tempTableName tar
829 INNER JOIN civicrm_activity {$this->_aliases['civicrm_activity']} ON {$this->_aliases['civicrm_activity']}.id = tar.civicrm_activity_id
830 INNER JOIN civicrm_activity_contact {$this->_aliases['civicrm_activity_contact']} ON {$this->_aliases['civicrm_activity_contact']}.activity_id = {$this->_aliases['civicrm_activity']}.id
831 AND {$this->_aliases['civicrm_activity_contact']}.record_type_id = {$sourceID}
832 LEFT JOIN civicrm_contact contact_civireport ON contact_civireport.id = {$this->_aliases['civicrm_activity_contact']}.contact_id
833 {$caseJoin}
834 {$this->_where} {$groupByFromSelect} {$this->_having} {$this->_orderBy} {$this->_limit}";
835
836 CRM_Utils_Hook::alterReportVar('sql', $this, $this);
837 $this->addToDeveloperTab($sql);
838
839 return $sql;
840 }
841
842 /**
843 * Override parent to reset value of activity_date.
844 */
845 public function beginPostProcessCommon() {
846 if (!empty($this->_resetDateFilter)) {
847 $this->_formValues['activity_date_time_relative'] = NULL;
848 }
849 }
850
851 /**
852 * Alter display of rows.
853 *
854 * Iterate through the rows retrieved via SQL and make changes for display purposes,
855 * such as rendering contacts as links.
856 *
857 * @param array $rows
858 * Rows generated by SQL, with an array for each row.
859 *
860 * @throws \CRM_Core_Exception
861 */
862 public function alterDisplay(&$rows) {
863 $entryFound = FALSE;
864 $activityType = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE);
865 $activityStatus = CRM_Core_PseudoConstant::activityStatus();
866 $priority = CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'priority_id');
867 $genders = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id');
868 $viewLinks = FALSE;
869
870 // Would we ever want to retrieve from the form controller??
871 $form = $this->noController ? NULL : $this;
872 $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $form, FALSE, 'report');
873 $actUrl = '';
874
875 if (CRM_Core_Permission::check('access CiviCRM')) {
876 $viewLinks = TRUE;
877 $onHover = ts('View Contact Summary for this Contact');
878 $onHoverAct = ts('View Activity Record');
879 }
880 foreach ($rows as $rowNum => $row) {
881 // if we have an activity type, format the View Activity link for use in various columns
882 if ($viewLinks &&
883 array_key_exists('civicrm_activity_activity_type_id', $row)
884 ) {
885 // Check for target contact id(s) and use the first contact id in that list for view activity link if found,
886 // else use source contact id
887 if (!empty($rows[$rowNum]['civicrm_contact_contact_target_id'])) {
888 $targets = explode(';', $rows[$rowNum]['civicrm_contact_contact_target_id']);
889 $cid = $targets[0];
890 }
891 else {
892 $cid = $rows[$rowNum]['civicrm_contact_contact_source_id'];
893 }
894
895 if (empty($this->_params['include_case_activities_value']) || empty($rows[$rowNum]['civicrm_case_activity_case_id'])) {
896 // Generate a "view activity" link
897 $actActionLinks = CRM_Activity_Selector_Activity::actionLinks($row['civicrm_activity_activity_type_id'],
898 CRM_Utils_Array::value('civicrm_activity_source_record_id', $rows[$rowNum]),
899 FALSE,
900 $rows[$rowNum]['civicrm_activity_id']
901 );
902
903 $actLinkValues = [
904 'id' => $rows[$rowNum]['civicrm_activity_id'],
905 'cid' => $cid,
906 'cxt' => $context,
907 ];
908 $actUrl = CRM_Utils_System::url($actActionLinks[CRM_Core_Action::VIEW]['url'],
909 CRM_Core_Action::replace($actActionLinks[CRM_Core_Action::VIEW]['qs'], $actLinkValues), TRUE
910 );
911 }
912 else {
913 // Generate a "view case activity" link
914 $caseActionLinks = CRM_Case_Selector_Search::actionLinks();
915 $caseLinkValues = [
916 'aid' => $rows[$rowNum]['civicrm_activity_id'],
917 'caseid' => $rows[$rowNum]['civicrm_case_activity_case_id'],
918 'cid' => $cid,
919 'cxt' => $context,
920 ];
921 $actUrl = CRM_Utils_System::url($caseActionLinks[CRM_Core_Action::VIEW]['url'],
922 CRM_Core_Action::replace($caseActionLinks[CRM_Core_Action::VIEW]['qs'], $caseLinkValues), TRUE
923 );
924 }
925 }
926
927 if (array_key_exists('civicrm_contact_contact_source', $row)) {
928 if ($value = $row['civicrm_contact_contact_source_id']) {
929 if ($viewLinks) {
930 $url = CRM_Utils_System::url("civicrm/contact/view",
931 'reset=1&cid=' . $value,
932 $this->_absoluteUrl
933 );
934 $rows[$rowNum]['civicrm_contact_contact_source_link'] = $url;
935 $rows[$rowNum]['civicrm_contact_contact_source_hover'] = $onHover;
936 }
937 $entryFound = TRUE;
938 }
939 }
940
941 if (array_key_exists('civicrm_contact_contact_assignee', $row)) {
942 $assigneeNames = explode(';', $row['civicrm_contact_contact_assignee']);
943 if ($value = $row['civicrm_contact_contact_assignee_id']) {
944 $assigneeContactIds = explode(';', $value);
945 $link = [];
946 if ($viewLinks) {
947 foreach ($assigneeContactIds as $id => $value) {
948 if (isset($value) && isset($assigneeNames[$id])) {
949 $url = CRM_Utils_System::url("civicrm/contact/view",
950 'reset=1&cid=' . $value,
951 $this->_absoluteUrl
952 );
953 $link[] = "<a title='" . $onHover . "' href='" . $url .
954 "'>{$assigneeNames[$id]}</a>";
955 }
956 }
957 $rows[$rowNum]['civicrm_contact_contact_assignee'] = implode('; ', $link);
958 }
959 $entryFound = TRUE;
960 }
961 }
962
963 if (array_key_exists('civicrm_contact_contact_target', $row)) {
964 $targetNames = explode(';', $row['civicrm_contact_contact_target']);
965 if ($value = $row['civicrm_contact_contact_target_id']) {
966 $targetContactIds = explode(';', $value);
967 $link = [];
968 if ($viewLinks) {
969 foreach ($targetContactIds as $id => $value) {
970 if (isset($value) && isset($targetNames[$id])) {
971 $url = CRM_Utils_System::url("civicrm/contact/view",
972 'reset=1&cid=' . $value,
973 $this->_absoluteUrl
974 );
975 $link[] = "<a title='" . $onHover . "' href='" . $url .
976 "'>{$targetNames[$id]}</a>";
977 }
978 }
979 $rows[$rowNum]['civicrm_contact_contact_target'] = implode('; ', $link);
980 }
981 $entryFound = TRUE;
982 }
983 }
984
985 if (array_key_exists('civicrm_activity_activity_type_id', $row)) {
986 if ($value = $row['civicrm_activity_activity_type_id']) {
987 $rows[$rowNum]['civicrm_activity_activity_type_id'] = $activityType[$value];
988 if ($viewLinks) {
989 $rows[$rowNum]['civicrm_activity_activity_type_id_link'] = $actUrl;
990 $rows[$rowNum]['civicrm_activity_activity_type_id_hover'] = $onHoverAct;
991 }
992 $entryFound = TRUE;
993 }
994 }
995
996 if (array_key_exists('civicrm_activity_status_id', $row)) {
997 if ($value = $row['civicrm_activity_status_id']) {
998 $rows[$rowNum]['civicrm_activity_status_id'] = $activityStatus[$value];
999 $entryFound = TRUE;
1000 }
1001 }
1002
1003 if (array_key_exists('civicrm_activity_priority_id', $row)) {
1004 if ($value = $row['civicrm_activity_priority_id']) {
1005 $rows[$rowNum]['civicrm_activity_priority_id'] = $priority[$value];
1006 $entryFound = TRUE;
1007 }
1008 }
1009
1010 if (array_key_exists('civicrm_activity_details', $row) && $this->_outputMode == 'html') {
1011 if ($value = $row['civicrm_activity_details']) {
1012 $fullDetails = $rows[$rowNum]['civicrm_activity_details'];
1013 $rows[$rowNum]['civicrm_activity_details'] = substr($fullDetails, 0, strrpos(substr($fullDetails, 0, 80), ' '));
1014 if ($actUrl) {
1015 $rows[$rowNum]['civicrm_activity_details'] .= " <a href='{$actUrl}' title='{$onHoverAct}'>(more)</a>";
1016 }
1017 $entryFound = TRUE;
1018 }
1019 }
1020
1021 if (array_key_exists('civicrm_activity_campaign_id', $row)) {
1022 if ($value = $row['civicrm_activity_campaign_id']) {
1023 $rows[$rowNum]['civicrm_activity_campaign_id'] = $this->campaigns[$value];
1024 $entryFound = TRUE;
1025 }
1026 }
1027
1028 if (array_key_exists('civicrm_activity_engagement_level', $row)) {
1029 if ($value = $row['civicrm_activity_engagement_level']) {
1030 $rows[$rowNum]['civicrm_activity_engagement_level'] = $this->engagementLevels[$value];
1031 $entryFound = TRUE;
1032 }
1033 }
1034
1035 if (array_key_exists('civicrm_activity_activity_date_time', $row) &&
1036 array_key_exists('civicrm_activity_status_id', $row)
1037 ) {
1038 if (CRM_Utils_Date::overdue($rows[$rowNum]['civicrm_activity_activity_date_time']) &&
1039 $activityStatus[$row['civicrm_activity_status_id']] != 'Completed'
1040 ) {
1041 $rows[$rowNum]['class'] = "status-overdue";
1042 $entryFound = TRUE;
1043 }
1044 }
1045
1046 if (array_key_exists('civicrm_contact_contact_target_gender', $row)) {
1047 if ($value = $row['civicrm_contact_contact_target_gender']) {
1048 $rows[$rowNum]['civicrm_contact_contact_target_gender'] = $genders[$value];
1049 $entryFound = TRUE;
1050 }
1051 }
1052
1053 $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, 'activity', 'List all activities for this', ';') ? TRUE : $entryFound;
1054
1055 if (!$entryFound) {
1056 break;
1057 }
1058 }
1059 }
1060
1061 public function sectionTotals() {
1062 // Reports using order_bys with sections must populate $this->_selectAliases in select() method.
1063 if (empty($this->_selectAliases)) {
1064 return;
1065 }
1066
1067 if (!empty($this->_sections)) {
1068 // pull section aliases out of $this->_sections
1069 $sectionAliases = array_keys($this->_sections);
1070
1071 $ifnulls = [];
1072 foreach (array_merge($sectionAliases, $this->_selectAliases) as $alias) {
1073 $ifnulls[] = "ifnull($alias, '') as $alias";
1074 }
1075 $this->_select = "SELECT " . implode(", ", $ifnulls);
1076 $this->_select = CRM_Contact_BAO_Query::appendAnyValueToSelect($ifnulls, $sectionAliases);
1077
1078 $query = $this->_select .
1079 ", count(DISTINCT civicrm_activity_id) as ct from {$this->temporaryTables['activity_temp_table']['name']} group by " .
1080 implode(", ", $sectionAliases);
1081
1082 // initialize array of total counts
1083 $totals = [];
1084 $dao = $this->executeReportQuery($query);
1085 while ($dao->fetch()) {
1086 // let $this->_alterDisplay translate any integer ids to human-readable values.
1087 $rows[0] = $dao->toArray();
1088 $this->alterDisplay($rows);
1089 $row = $rows[0];
1090
1091 // add totals for all permutations of section values
1092 $values = [];
1093 $i = 1;
1094 $aliasCount = count($sectionAliases);
1095 foreach ($sectionAliases as $alias) {
1096 $values[] = $row[$alias];
1097 $key = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values);
1098 if ($i == $aliasCount) {
1099 // the last alias is the lowest-level section header; use count as-is
1100 $totals[$key] = $dao->ct;
1101 }
1102 else {
1103 // other aliases are higher level; roll count into their total
1104 $totals[$key] += $dao->ct;
1105 }
1106 }
1107 }
1108 $this->assign('sectionTotals', $totals);
1109 }
1110 }
1111
1112 /**
1113 * @todo remove this function & declare the 3 contact tables separately
1114 *
1115 * (Currently the construct method incorrectly melds them - this is an interim
1116 * refactor in order to get this under ReportTemplateTests)
1117 */
1118 protected function buildAssigneeFrom() {
1119 $this->buildFrom('assignee');
1120 }
1121
1122 /**
1123 * @todo remove this function & declare the 3 contact tables separately
1124 *
1125 * (Currently the construct method incorrectly melds them - this is an interim
1126 * refactor in order to get this under ReportTemplateTests)
1127 */
1128 protected function buildSourceFrom() {
1129 $this->buildFrom('source');
1130 }
1131
1132 /**
1133 * Shared function to build the from clause
1134 *
1135 * @param string $recordType (one of 'source', 'activity', 'target')
1136 */
1137 protected function buildFrom($recordType) {
1138 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
1139 switch ($recordType) {
1140 case 'target':
1141 $recordTypeID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
1142 break;
1143
1144 case 'source':
1145 $recordTypeID = CRM_Utils_Array::key('Activity Source', $activityContacts);
1146 break;
1147
1148 case 'assignee':
1149 $recordTypeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
1150 break;
1151
1152 }
1153
1154 $this->_from = "
1155 FROM civicrm_activity {$this->_aliases['civicrm_activity']}
1156 INNER JOIN civicrm_activity_contact {$this->_aliases['civicrm_activity_contact']}
1157 ON {$this->_aliases['civicrm_activity']}.id = {$this->_aliases['civicrm_activity_contact']}.activity_id AND
1158 {$this->_aliases['civicrm_activity_contact']}.record_type_id = {$recordTypeID}
1159 INNER JOIN civicrm_contact civicrm_contact_{$recordType}
1160 ON {$this->_aliases['civicrm_activity_contact']}.contact_id = civicrm_contact_{$recordType}.id
1161 {$this->_aclFrom}";
1162 if (!empty($this->_params['include_case_activities_value'])) {
1163 $this->_from .= "
1164 LEFT JOIN civicrm_case_activity {$this->_aliases['civicrm_case_activity']}
1165 ON {$this->_aliases['civicrm_case_activity']}.activity_id = {$this->_aliases['civicrm_activity']}.id";
1166 }
1167
1168 if ($this->isTableSelected('civicrm_email')) {
1169 $this->_from .= "
1170 LEFT JOIN civicrm_email civicrm_email_{$recordType}
1171 ON {$this->_aliases['civicrm_activity_contact']}.contact_id = civicrm_email_{$recordType}.contact_id AND
1172 civicrm_email_{$recordType}.is_primary = 1";
1173 }
1174
1175 if ($this->isTableSelected('civicrm_phone')) {
1176 $this->_from .= "
1177 LEFT JOIN civicrm_phone civicrm_phone_{$recordType}
1178 ON {$this->_aliases['civicrm_activity_contact']}.contact_id = civicrm_phone_{$recordType}.contact_id AND
1179 civicrm_phone_{$recordType}.is_primary = 1 ";
1180 }
1181 $this->_aliases['civicrm_contact'] = "civicrm_contact_{$recordType}";
1182
1183 $this->joinAddressFromContact();
1184 }
1185
1186 }