Merge pull request #18449 from colemanw/multiValueAutocomplete
[civicrm-core.git] / CRM / Case / BAO / Case.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
3e120a63 18
6a488035 19/**
cde2037d 20 * This class contains the functions for Case Management.
6a488035
TO
21 */
22class CRM_Case_BAO_Case extends CRM_Case_DAO_Case {
23
24 /**
cde2037d 25 * Static field for all the case information that we can potentially export.
6a488035
TO
26 *
27 * @var array
6a488035 28 */
f157740d 29 public static $_exportableFields = NULL;
e96f025a 30
4c6ce474 31 /**
cde2037d 32 * Class constructor.
4c6ce474 33 */
00be9182 34 public function __construct() {
6a488035
TO
35 parent::__construct();
36 }
37
38 /**
077dbf5e
CW
39 * Is CiviCase enabled?
40 *
41 * @return bool
42 */
00be9182 43 public static function enabled() {
077dbf5e
CW
44 $config = CRM_Core_Config::singleton();
45 return in_array('CiviCase', $config->enableComponents);
46 }
47
6a488035 48 /**
cde2037d 49 * Create a case object.
6a488035 50 *
cde2037d 51 * The function extracts all the params it needs to initialize the create a
6a488035
TO
52 * case object. the params array could contain additional unused name/value
53 * pairs
54 *
64bd5a0e
TO
55 * @param array $params
56 * (reference ) an assoc array of name/value pairs.
77b97be7 57 *
16b10e64 58 * @return CRM_Case_BAO_Case
6a488035 59 */
00be9182 60 public static function add(&$params) {
6a488035
TO
61 $caseDAO = new CRM_Case_DAO_Case();
62 $caseDAO->copyValues($params);
da4d8910 63 $result = $caseDAO->save();
5d4fcf54
TO
64 // Get other case values (required by XML processor), this adds to $result array
65 $caseDAO->find(TRUE);
da4d8910 66 return $result;
6a488035
TO
67 }
68
6a488035 69 /**
cde2037d 70 * Takes an associative array and creates a case object.
6a488035 71 *
64bd5a0e 72 * @param array $params
cde2037d 73 * (reference) an assoc array of name/value pairs.
77b97be7 74 *
16b10e64 75 * @return CRM_Case_BAO_Case
6a488035 76 */
00be9182 77 public static function &create(&$params) {
ea6a17a9
TO
78 // CRM-20958 - These fields are managed by MySQL triggers. Watch out for clients resaving stale timestamps.
79 unset($params['created_date']);
80 unset($params['modified_date']);
3717c347
JP
81 $caseStatus = CRM_Case_PseudoConstant::caseStatus('name');
82 // for resolved case the end date should set to now
83 if (!empty($params['status_id']) && $params['status_id'] == array_search('Closed', $caseStatus)) {
84 $params['end_date'] = date("Ymd");
85 }
ea6a17a9 86
6a488035
TO
87 $transaction = new CRM_Core_Transaction();
88
a7488080 89 if (!empty($params['id'])) {
6a488035
TO
90 CRM_Utils_Hook::pre('edit', 'Case', $params['id'], $params);
91 }
92 else {
93 CRM_Utils_Hook::pre('create', 'Case', NULL, $params);
94 }
95
96 $case = self::add($params);
97
a7488080 98 if (!empty($params['custom']) &&
6a488035
TO
99 is_array($params['custom'])
100 ) {
101 CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_case', $case->id);
102 }
103
104 if (is_a($case, 'CRM_Core_Error')) {
105 $transaction->rollback();
106 return $case;
107 }
108
a7488080 109 if (!empty($params['id'])) {
6a488035
TO
110 CRM_Utils_Hook::post('edit', 'Case', $case->id, $case);
111 }
112 else {
113 CRM_Utils_Hook::post('create', 'Case', $case->id, $case);
114 }
115 $transaction->commit();
116
117 //we are not creating log for case
118 //since case log can be tracked using log for activity.
119 return $case;
120 }
121
6a488035 122 /**
100fef9d 123 * Process case activity add/delete
6a488035
TO
124 * takes an associative array and
125 *
64bd5a0e
TO
126 * @param array $params
127 * (reference ) an assoc array of name/value pairs.
6a488035 128 *
6a488035 129 */
00be9182 130 public static function processCaseActivity(&$params) {
6a488035
TO
131 $caseActivityDAO = new CRM_Case_DAO_CaseActivity();
132 $caseActivityDAO->activity_id = $params['activity_id'];
133 $caseActivityDAO->case_id = $params['case_id'];
134
135 $caseActivityDAO->find(TRUE);
136 $caseActivityDAO->save();
137 }
138
139 /**
cde2037d 140 * Get the case subject for Activity.
6a488035 141 *
64bd5a0e
TO
142 * @param int $activityId
143 * Activity id.
6a488035 144 *
2b37475d 145 * @return string|null
6a488035 146 */
00be9182 147 public static function getCaseSubject($activityId) {
6a488035
TO
148 $caseActivity = new CRM_Case_DAO_CaseActivity();
149 $caseActivity->activity_id = $activityId;
150 if ($caseActivity->find(TRUE)) {
151 return CRM_Core_DAO::getFieldValue('CRM_Case_BAO_Case', $caseActivity->case_id, 'subject');
152 }
153 return NULL;
154 }
155
156 /**
100fef9d 157 * Get the case type.
6a488035
TO
158 *
159 * @param int $caseId
77b97be7
EM
160 * @param string $colName
161 *
a6c01b45
CW
162 * @return string
163 * case type
6a488035 164 */
00be9182 165 public static function getCaseType($caseId, $colName = 'title') {
8ffdec17
ARW
166 $query = "
167SELECT civicrm_case_type.{$colName} FROM civicrm_case
168LEFT JOIN civicrm_case_type ON
169 civicrm_case.case_type_id = civicrm_case_type.id
170WHERE civicrm_case.id = %1";
6a488035 171
fc9f05e0 172 $queryParams = [1 => [$caseId, 'Integer']];
6a488035 173
8ffdec17 174 return CRM_Core_DAO::singleValueQuery($query, $queryParams);
6a488035
TO
175 }
176
177 /**
d2e5d2ce 178 * Delete the record that are associated with this case.
6a488035
TO
179 * record are deleted from case
180 *
64bd5a0e
TO
181 * @param int $caseId
182 * Id of the case to delete.
6a488035 183 *
77b97be7
EM
184 * @param bool $moveToTrash
185 *
a6c01b45
CW
186 * @return bool
187 * is successful
6a488035 188 */
00be9182 189 public static function deleteCase($caseId, $moveToTrash = FALSE) {
6a488035
TO
190 CRM_Utils_Hook::pre('delete', 'Case', $caseId, CRM_Core_DAO::$_nullArray);
191
192 //delete activities
193 $activities = self::getCaseActivityDates($caseId);
194 if ($activities) {
195 foreach ($activities as $value) {
196 CRM_Activity_BAO_Activity::deleteActivity($value, $moveToTrash);
197 }
198 }
199
200 if (!$moveToTrash) {
201 $transaction = new CRM_Core_Transaction();
202 }
203 $case = new CRM_Case_DAO_Case();
204 $case->id = $caseId;
205 if (!$moveToTrash) {
206 $result = $case->delete();
207 $transaction->commit();
208 }
209 else {
210 $result = $case->is_deleted = 1;
211 $case->save();
212 }
213
214 if ($result) {
215 // CRM-7364, disable relationships
216 self::enableDisableCaseRelationships($caseId, FALSE);
217
218 CRM_Utils_Hook::post('delete', 'Case', $caseId, $case);
219
220 // remove case from recent items.
fc9f05e0 221 $caseRecent = [
6a488035
TO
222 'id' => $caseId,
223 'type' => 'Case',
fc9f05e0 224 ];
6a488035
TO
225 CRM_Utils_Recent::del($caseRecent);
226 return TRUE;
227 }
228
229 return FALSE;
230 }
231
232 /**
cde2037d 233 * Enable disable case related relationships.
6a488035 234 *
64bd5a0e
TO
235 * @param int $caseId
236 * Case id.
237 * @param bool $enable
238 * Action.
6a488035 239 */
00be9182 240 public static function enableDisableCaseRelationships($caseId, $enable) {
6a488035
TO
241 $contactIds = self::retrieveContactIdsByCaseId($caseId);
242 if (!empty($contactIds)) {
243 foreach ($contactIds as $cid) {
244 $roles = self::getCaseRoles($cid, $caseId);
245 if (!empty($roles)) {
246 $relationshipIds = implode(',', array_keys($roles));
e96f025a 247 $enable = (int) $enable;
248 $query = "UPDATE civicrm_relationship SET is_active = {$enable}
6a488035
TO
249 WHERE id IN ( {$relationshipIds} )";
250 CRM_Core_DAO::executeQuery($query);
251 }
252 }
253 }
254 }
255
6a488035 256 /**
fe482240 257 * Retrieve contact_id by case_id.
6a488035 258 *
64bd5a0e
TO
259 * @param int $caseId
260 * ID of the case.
77b97be7 261 *
100fef9d 262 * @param int $contactID
ea2aa8ff 263 * @param int $startArrayAt This is to support legacy calls to Case.Get API which may rely on the first array index being set to 1
6a488035
TO
264 *
265 * @return array
6a488035 266 */
ea2aa8ff 267 public static function retrieveContactIdsByCaseId($caseId, $contactID = NULL, $startArrayAt = 0) {
6a488035
TO
268 $caseContact = new CRM_Case_DAO_CaseContact();
269 $caseContact->case_id = $caseId;
270 $caseContact->find();
fc9f05e0 271 $contactArray = [];
ea2aa8ff 272 $count = $startArrayAt;
6a488035
TO
273 while ($caseContact->fetch()) {
274 if ($contactID != $caseContact->contact_id) {
275 $contactArray[$count] = $caseContact->contact_id;
276 $count++;
277 }
278 }
279
280 return $contactArray;
281 }
282
283 /**
cde2037d 284 * Look up a case using an activity ID.
6a488035 285 *
100fef9d 286 * @param int $activityId
77b97be7 287 *
3e120a63 288 * @return int|null, case ID
6a488035 289 */
00be9182 290 public static function getCaseIdByActivityId($activityId) {
6a488035
TO
291 $originalId = CRM_Core_DAO::singleValueQuery(
292 'SELECT original_id FROM civicrm_activity WHERE id = %1',
fc9f05e0 293 ['1' => [$activityId, 'Integer']]
6a488035
TO
294 );
295 $caseId = CRM_Core_DAO::singleValueQuery(
296 'SELECT case_id FROM civicrm_case_activity WHERE activity_id in (%1,%2)',
fc9f05e0 297 [
298 '1' => [$activityId, 'Integer'],
299 '2' => [$originalId ? $originalId : $activityId, 'Integer'],
300 ]
6a488035
TO
301 );
302 return $caseId;
303 }
304
305 /**
d2e5d2ce 306 * Retrieve contact names by caseId.
6a488035 307 *
64bd5a0e
TO
308 * @param int $caseId
309 * ID of the case.
6a488035
TO
310 *
311 * @return array
6a488035 312 */
00be9182 313 public static function getContactNames($caseId) {
fc9f05e0 314 $contactNames = [];
6a488035
TO
315 if (!$caseId) {
316 return $contactNames;
317 }
318
319 $query = "
320 SELECT contact_a.sort_name name,
321 contact_a.display_name as display_name,
322 contact_a.id cid,
323 contact_a.birth_date as birth_date,
324 ce.email as email,
325 cp.phone as phone
326 FROM civicrm_contact contact_a
327 LEFT JOIN civicrm_case_contact ON civicrm_case_contact.contact_id = contact_a.id
328 LEFT JOIN civicrm_email ce ON ( ce.contact_id = contact_a.id AND ce.is_primary = 1)
329 LEFT JOIN civicrm_phone cp ON ( cp.contact_id = contact_a.id AND cp.is_primary = 1)
6b576a7c 330 WHERE contact_a.is_deleted = 0 AND civicrm_case_contact.case_id = %1
78762324 331 ORDER BY civicrm_case_contact.id";
6a488035
TO
332
333 $dao = CRM_Core_DAO::executeQuery($query,
fc9f05e0 334 [1 => [$caseId, 'Integer']]
6a488035
TO
335 );
336 while ($dao->fetch()) {
337 $contactNames[$dao->cid]['contact_id'] = $dao->cid;
338 $contactNames[$dao->cid]['sort_name'] = $dao->name;
339 $contactNames[$dao->cid]['display_name'] = $dao->display_name;
340 $contactNames[$dao->cid]['email'] = $dao->email;
341 $contactNames[$dao->cid]['phone'] = $dao->phone;
342 $contactNames[$dao->cid]['birth_date'] = $dao->birth_date;
343 $contactNames[$dao->cid]['role'] = ts('Client');
344 }
345
346 return $contactNames;
347 }
348
349 /**
cde2037d 350 * Retrieve case_id by contact_id.
6a488035 351 *
100fef9d 352 * @param int $contactID
64bd5a0e
TO
353 * @param bool $includeDeleted
354 * Include the deleted cases in result.
77b97be7
EM
355 * @param null $caseType
356 *
6a488035 357 * @return array
6a488035 358 */
00be9182 359 public static function retrieveCaseIdsByContactId($contactID, $includeDeleted = FALSE, $caseType = NULL) {
6a488035
TO
360 $query = "
361SELECT ca.id as id
362FROM civicrm_case_contact cc
363INNER JOIN civicrm_case ca ON cc.case_id = ca.id
6a488035 364";
1d8da9d9
DJ
365 if (isset($caseType)) {
366 $query .=
76304e2f 367 "INNER JOIN civicrm_case_type ON civicrm_case_type.id = ca.case_type_id
8ffdec17 368WHERE cc.contact_id = %1 AND civicrm_case_type.name = '{$caseType}'";
1d8da9d9 369 }
a2da6067
DJ
370 if (!isset($caseType)) {
371 $query .= "WHERE cc.contact_id = %1";
372 }
6a488035
TO
373 if (!$includeDeleted) {
374 $query .= " AND ca.is_deleted = 0";
375 }
376
fc9f05e0 377 $params = [1 => [$contactID, 'Integer']];
6a488035
TO
378 $dao = CRM_Core_DAO::executeQuery($query, $params);
379
fc9f05e0 380 $caseArray = [];
6a488035
TO
381 while ($dao->fetch()) {
382 $caseArray[] = $dao->id;
383 }
384
6a488035
TO
385 return $caseArray;
386 }
387
4c6ce474
EM
388 /**
389 * @param string $type
100fef9d 390 * @param int $userID
d6eb0c11 391 * @param string $condition
4c6ce474
EM
392 *
393 * @return string
394 */
5f1c8c57 395 public static function getCaseActivityCountQuery($type = 'upcoming', $userID, $condition = NULL) {
396 return sprintf(" SELECT COUNT(*) FROM (%s) temp ", self::getCaseActivityQuery($type, $userID, $condition));
397 }
6a488035 398
5f1c8c57 399 /**
400 * @param string $type
401 * @param int $userID
402 * @param string $condition
403 * @param string $limit
f157740d 404 * @param string $order
5f1c8c57 405 *
406 * @return string
407 */
408 public static function getCaseActivityQuery($type = 'upcoming', $userID, $condition = NULL, $limit = NULL, $order = NULL) {
fc9f05e0 409 $selectClauses = [
5f1c8c57 410 'civicrm_case.id as case_id',
411 'civicrm_case.subject as case_subject',
412 'civicrm_contact.id as contact_id',
413 'civicrm_contact.sort_name as sort_name',
414 'civicrm_phone.phone as phone',
415 'civicrm_contact.contact_type as contact_type',
416 'civicrm_contact.contact_sub_type as contact_sub_type',
8cf4fbdb 417 't_act.activity_type_id as activity_type_id',
5f1c8c57 418 'civicrm_case.case_type_id as case_type_id',
57d38398 419 'civicrm_case.status_id as case_status_id',
8cf4fbdb 420 't_act.status_id as status_id',
5f1c8c57 421 'civicrm_case.start_date as case_start_date',
41cf58d3 422 "GROUP_CONCAT(DISTINCT IF(case_relationship.contact_id_b = $userID, case_relation_type.label_a_b, case_relation_type.label_b_a) SEPARATOR ', ') as case_role",
2c783cce
AH
423 't_act.activity_date_time as activity_date_time',
424 't_act.id as activity_id',
fc9f05e0 425 ];
6a488035 426
5f1c8c57 427 $query = CRM_Contact_BAO_Query::appendAnyValueToSelect($selectClauses, 'case_id');
428
9d22ae15
AH
429 $query .= <<<HERESQL
430 FROM civicrm_case
431 INNER JOIN civicrm_case_contact ON civicrm_case.id = civicrm_case_contact.case_id
432 INNER JOIN civicrm_contact ON civicrm_case_contact.contact_id = civicrm_contact.id
433HERESQL;
434
435 switch ($type) {
436 case 'upcoming':
437 case 'recent':
438 // civicrm_view_case_activity_upcoming and
439 // civicrm_view_case_activity_recent are views that show the next
440 // scheduled and most recent not-scheduled activity on each case,
441 // respectively.
442 $query .= <<<HERESQL
443 INNER JOIN civicrm_view_case_activity_$type t_act
444 ON t_act.case_id = civicrm_case.id
445HERESQL;
446 break;
447
448 case 'any':
449 $query .= <<<HERESQL
450 LEFT JOIN civicrm_case_activity ca4
451 ON civicrm_case.id = ca4.case_id
452 LEFT JOIN civicrm_activity t_act
453 ON t_act.id = ca4.activity_id
454 AND t_act.is_current_revision = 1
455HERESQL;
90319720 456 }
6a488035 457
9d22ae15
AH
458 $query .= <<<HERESQL
459 LEFT JOIN civicrm_phone
460 ON civicrm_phone.contact_id = civicrm_contact.id
461 AND civicrm_phone.is_primary = 1
462 LEFT JOIN civicrm_relationship case_relationship
41cf58d3
AF
463 ON ((case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID})
464 OR (case_relationship.contact_id_b = civicrm_case_contact.contact_id AND case_relationship.contact_id_a = {$userID}))
9d22ae15
AH
465 AND case_relationship.is_active
466 AND case_relationship.case_id = civicrm_case.id
467 LEFT JOIN civicrm_relationship_type case_relation_type
468 ON case_relation_type.id = case_relationship.relationship_type_id
469 AND case_relation_type.id = case_relationship.relationship_type_id
470HERESQL;
6a488035
TO
471
472 if ($condition) {
473 // CRM-8749 backwards compatibility - callers of this function expect to start $condition with "AND"
5f1c8c57 474 $query .= " WHERE (1) AND $condition ";
6a488035 475 }
5f1c8c57 476 $query .= " GROUP BY case_id ";
6a488035 477
2c783cce 478 $query .= ($order) ?: ' ORDER BY activity_date_time ASC';
5f1c8c57 479
480 if ($limit) {
481 $query .= $limit;
90319720 482 }
6a488035
TO
483
484 return $query;
485 }
486
487 /**
cde2037d 488 * Retrieve cases related to particular contact or whole contact used in Dashboard and Tab.
6a488035 489 *
64bd5a0e 490 * @param bool $allCases
5f1c8c57 491 * @param array $params
77b97be7 492 * @param string $context
5f1c8c57 493 * @param bool $getCount
77b97be7 494 *
a6c01b45
CW
495 * @return array
496 * Array of Cases
6a488035 497 */
fc9f05e0 498 public static function getCases($allCases = TRUE, $params = [], $context = 'dashboard', $getCount = FALSE) {
6a488035 499 $condition = NULL;
fc9f05e0 500 $casesList = [];
6a488035 501
d83ee565 502 // validate access for own cases.
6a488035 503 if (!self::accessCiviCase()) {
5f1c8c57 504 return $getCount ? 0 : $casesList;
6a488035
TO
505 }
506
d83ee565
MWMC
507 // Return cached value instead of re-running query
508 if (isset(Civi::$statics[__CLASS__]['totalCount']) && $getCount) {
509 return Civi::$statics[__CLASS__]['totalCount'];
510 }
511
5f1c8c57 512 $type = CRM_Utils_Array::value('type', $params, 'upcoming');
f0c3f9bc 513 $userID = CRM_Core_Session::getLoggedInContactID();
5f1c8c57 514
d83ee565 515 // validate access for all cases.
6a488035
TO
516 if ($allCases && !CRM_Core_Permission::check('access all cases and activities')) {
517 $allCases = FALSE;
518 }
519
fc9f05e0 520 $whereClauses = ['civicrm_case.is_deleted = 0 AND civicrm_contact.is_deleted <> 1'];
6a488035
TO
521
522 if (!$allCases) {
41cf58d3
AF
523 $whereClauses[] = "(case_relationship.contact_id_b = {$userID} OR case_relationship.contact_id_a = {$userID})";
524 $whereClauses[] = 'case_relationship.is_active';
6a488035 525 }
5f1c8c57 526 if (empty($params['status_id']) && ($type == 'upcoming' || $type == 'any')) {
41cf58d3 527 $whereClauses[] = "civicrm_case.status_id != " . CRM_Core_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Closed');
6a488035
TO
528 }
529
fc9f05e0 530 foreach (['case_type_id', 'status_id'] as $column) {
5f1c8c57 531 if (!empty($params[$column])) {
532 $whereClauses[] = sprintf("civicrm_case.%s IN (%s)", $column, $params[$column]);
533 }
534 }
535 $condition = implode(' AND ', $whereClauses);
6a488035 536
d83ee565 537 Civi::$statics[__CLASS__]['totalCount'] = $totalCount = CRM_Core_DAO::singleValueQuery(self::getCaseActivityCountQuery($type, $userID, $condition));
5f1c8c57 538 if ($getCount) {
539 return $totalCount;
6a488035 540 }
5f1c8c57 541 $casesList['total'] = $totalCount;
542
543 $limit = '';
544 if (!empty($params['rp'])) {
545 $params['offset'] = ($params['page'] - 1) * $params['rp'];
546 $params['rowCount'] = $params['rp'];
547 if (!empty($params['rowCount']) && $params['rowCount'] > 0) {
548 $limit = " LIMIT {$params['offset']}, {$params['rowCount']} ";
549 }
6a488035 550 }
5f1c8c57 551
552 $order = NULL;
553 if (!empty($params['sortBy'])) {
554 if (strstr($params['sortBy'], 'date ')) {
2c783cce 555 $params['sortBy'] = str_replace('date', 'activity_date_time', $params['sortBy']);
5f1c8c57 556 }
557 $order = "ORDER BY " . $params['sortBy'];
90319720 558 }
6a488035 559
5f1c8c57 560 $query = self::getCaseActivityQuery($type, $userID, $condition, $limit, $order);
561 $result = CRM_Core_DAO::executeQuery($query);
562
6a488035
TO
563 // we're going to use the usual actions, so doesn't make sense to duplicate definitions
564 $actions = CRM_Case_Selector_Search::links();
565
6a488035 566 // check is the user has view/edit signer permission
fc9f05e0 567 $permissions = [CRM_Core_Permission::VIEW];
6a488035
TO
568 if (CRM_Core_Permission::check('access all cases and activities') ||
569 (!$allCases && CRM_Core_Permission::check('access my cases and activities'))
570 ) {
571 $permissions[] = CRM_Core_Permission::EDIT;
572 }
573 if (CRM_Core_Permission::check('delete in CiviCase')) {
574 $permissions[] = CRM_Core_Permission::DELETE;
575 }
576 $mask = CRM_Core_Action::mask($permissions);
577
2c783cce
AH
578 // Pseudoconstants to populate labels
579 $caseStatuses = CRM_Case_PseudoConstant::caseStatus('label', FALSE);
5f1c8c57 580 $caseTypes = CRM_Case_PseudoConstant::caseType('name');
57d38398 581 $caseTypeTitles = CRM_Case_PseudoConstant::caseType('title', FALSE);
2c783cce
AH
582 $activityTypeLabels = CRM_Activity_BAO_Activity::buildOptions('activity_type_id');
583
bf5990e7 584 foreach ($result->fetchAll() as $case) {
585 $key = $case['case_id'];
fc9f05e0 586 $casesList[$key] = [];
5f1c8c57 587 $casesList[$key]['DT_RowId'] = $case['case_id'];
fc9f05e0 588 $casesList[$key]['DT_RowAttr'] = ['data-entity' => 'case', 'data-id' => $case['case_id']];
5f1c8c57 589 $casesList[$key]['DT_RowClass'] = "crm-entity";
590
591 $casesList[$key]['activity_list'] = sprintf('<a title="%s" class="crm-expand-row" href="%s"></a>',
592 ts('Activities'),
fc9f05e0 593 CRM_Utils_System::url('civicrm/case/details', ['caseId' => $case['case_id'], 'cid' => $case['contact_id'], 'type' => $type])
5f1c8c57 594 );
595
596 $phone = empty($case['phone']) ? '' : '<br /><span class="description">' . $case['phone'] . '</span>';
d7512022 597 $casesList[$key]['sort_name'] = sprintf('<a href="%s">%s</a>%s<br /><span class="description">%s: %d</span>',
fc9f05e0 598 CRM_Utils_System::url('civicrm/contact/view', ['cid' => $case['contact_id']]),
5f1c8c57 599 $case['sort_name'],
600 $phone,
601 ts('Case ID'),
602 $case['case_id']
603 );
604 $casesList[$key]['subject'] = $case['case_subject'];
9c1bc317 605 $casesList[$key]['case_status'] = $caseStatuses[$case['case_status_id']] ?? NULL;
2c783cce 606 if ($case['case_status_id'] == CRM_Case_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Urgent')) {
57d38398
AH
607 $casesList[$key]['case_status'] = sprintf('<strong>%s</strong>', strtoupper($casesList[$key]['case_status']));
608 }
9c1bc317 609 $casesList[$key]['case_type'] = $caseTypeTitles[$case['case_type_id']] ?? NULL;
5f1c8c57 610 $casesList[$key]['case_role'] = CRM_Utils_Array::value('case_role', $case, '---');
c00bd201 611 $casesList[$key]['manager'] = self::getCaseManagerContact($caseTypes[$case['case_type_id']], $case['case_id']);
5f1c8c57 612
9c1bc317 613 $casesList[$key]['date'] = $activityTypeLabels[$case['activity_type_id']] ?? NULL;
2c783cce 614 if ($actId = CRM_Utils_Array::value('activity_id', $case)) {
5f1c8c57 615 if (self::checkPermission($actId, 'view', $case['activity_type_id'], $userID)) {
616 if ($type == 'recent') {
617 $casesList[$key]['date'] = sprintf('<a class="action-item crm-hover-button" href="%s" title="%s">%s</a>',
fc9f05e0 618 CRM_Utils_System::url('civicrm/case/activity/view', ['reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case['activity_id']]),
5f1c8c57 619 ts('View activity'),
2c783cce 620 CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels)
5f1c8c57 621 );
6a488035
TO
622 }
623 else {
2c783cce 624 $status = CRM_Utils_Date::overdue($case['activity_date_time']) ? 'status-overdue' : 'status-scheduled';
5f1c8c57 625 $casesList[$key]['date'] = sprintf('<a class="crm-popup %s" href="%s" title="%s">%s</a> &nbsp;&nbsp;',
fc9f05e0 626 $status,
627 CRM_Utils_System::url('civicrm/case/activity/view', ['reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case['activity_id']]),
5f1c8c57 628 ts('View activity'),
2c783cce 629 CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels)
5f1c8c57 630 );
6a488035
TO
631 }
632 }
cedb74cd 633 if (isset($case['activity_type_id']) && self::checkPermission($actId, 'edit', $case['activity_type_id'], $userID)) {
13a3d214 634 $casesList[$key]['date'] .= sprintf('<a class="action-item crm-hover-button" href="%s" title="%s"><i class="crm-i fa-pencil" aria-hidden="true"></i></a>',
fc9f05e0 635 CRM_Utils_System::url('civicrm/case/activity', ['reset' => 1, 'cid' => $case['contact_id'], 'caseid' => $case['case_id'], 'action' => 'update', 'id' => $actId]),
5f1c8c57 636 ts('Edit activity')
637 );
638 }
6a488035 639 }
2c783cce 640 $casesList[$key]['date'] .= "<br/>" . CRM_Utils_Date::customFormat($case['activity_date_time']);
5f1c8c57 641 $casesList[$key]['links'] = CRM_Core_Action::formLink($actions['primaryActions'], $mask,
fc9f05e0 642 [
5f1c8c57 643 'id' => $case['case_id'],
644 'cid' => $case['contact_id'],
645 'cxt' => $context,
fc9f05e0 646 ],
5f1c8c57 647 ts('more'),
648 FALSE,
649 'case.actions.primary',
650 'Case',
651 $case['case_id']
652 );
6a488035
TO
653 }
654
655 return $casesList;
656 }
657
658 /**
100fef9d
CW
659 * Get the summary of cases counts by type and status.
660 *
661 * @param bool $allCases
fc9f05e0 662 *
100fef9d 663 * @return array
6a488035 664 */
5f1c8c57 665 public static function getCasesSummary($allCases = TRUE) {
fc9f05e0 666 $caseSummary = [];
6a488035
TO
667
668 //validate access for civicase.
669 if (!self::accessCiviCase()) {
670 return $caseSummary;
671 }
672
f0c3f9bc 673 $userID = CRM_Core_Session::getLoggedInContactID();
5f1c8c57 674
6a488035
TO
675 //validate access for all cases.
676 if ($allCases && !CRM_Core_Permission::check('access all cases and activities')) {
677 $allCases = FALSE;
678 }
679
e96f025a 680 $caseTypes = CRM_Case_PseudoConstant::caseType();
6a488035 681 $caseStatuses = CRM_Case_PseudoConstant::caseStatus();
e96f025a 682 $caseTypes = array_flip($caseTypes);
6a488035
TO
683
684 // get statuses as headers for the table
685 $url = CRM_Utils_System::url('civicrm/case/search', "reset=1&force=1&all=1&status=");
686 foreach ($caseStatuses as $key => $name) {
687 $caseSummary['headers'][$key]['status'] = $name;
688 $caseSummary['headers'][$key]['url'] = $url . $key;
689 }
690
691 // build rows with actual data
fc9f05e0 692 $rows = [];
41cf58d3 693 $myGroupByClause = $mySelectClause = $myCaseFromClause = $myCaseWhereClauseA = $myCaseWhereClauseB = '';
6a488035
TO
694
695 if ($allCases) {
696 $userID = 'null';
697 $all = 1;
698 $case_owner = 1;
41cf58d3 699 $myGroupByClauseB = ' GROUP BY civicrm_case.id';
6a488035
TO
700 }
701 else {
e96f025a 702 $all = 0;
6a488035 703 $case_owner = 2;
41cf58d3
AF
704 $myCaseWhereClauseA = " AND case_relationship.contact_id_a = {$userID} AND case_relationship.is_active ";
705 $myGroupByClauseA = " GROUP BY CONCAT(civicrm_case.id,'-',case_relationship.contact_id_a)";
706 $myCaseWhereClauseB = " AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active ";
707 $myGroupByClauseB = " GROUP BY CONCAT(civicrm_case.id,'-',case_relationship.contact_id_b)";
6a488035 708 }
41cf58d3
AF
709 $myGroupByClauseB .= ", case_status.label, status_id, case_type_id, civicrm_case.id";
710 $myGroupByClauseA = $myGroupByClauseB;
d5bea1e3 711 // FIXME: This query could be a lot more efficient if it used COUNT() instead of returning all rows and then counting them with php
6a488035 712 $query = "
a1afe798 713SELECT civicrm_case.id, case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type,
41cf58d3 714 case_type_id, case_relationship.contact_id_b as case_contact
6a488035 715 FROM civicrm_case
d5bea1e3 716 INNER JOIN civicrm_case_contact cc on cc.case_id = civicrm_case.id
8ffdec17 717 LEFT JOIN civicrm_case_type ON civicrm_case.case_type_id = civicrm_case_type.id
6a488035
TO
718 LEFT JOIN civicrm_option_group option_group_case_status ON ( option_group_case_status.name = 'case_status' )
719 LEFT JOIN civicrm_option_value case_status ON ( civicrm_case.status_id = case_status.value
720 AND option_group_case_status.id = case_status.option_group_id )
721 LEFT JOIN civicrm_relationship case_relationship ON ( case_relationship.case_id = civicrm_case.id
aee55e82 722 AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active )
d5bea1e3 723 WHERE is_deleted = 0 AND cc.contact_id IN (SELECT id FROM civicrm_contact WHERE is_deleted <> 1)
41cf58d3
AF
724{$myCaseWhereClauseB} {$myGroupByClauseB}
725UNION
a1afe798 726SELECT civicrm_case.id, case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type,
41cf58d3
AF
727 case_type_id, case_relationship.contact_id_a as case_contact
728 FROM civicrm_case
729 INNER JOIN civicrm_case_contact cc on cc.case_id = civicrm_case.id
730 LEFT JOIN civicrm_case_type ON civicrm_case.case_type_id = civicrm_case_type.id
731 LEFT JOIN civicrm_option_group option_group_case_status ON ( option_group_case_status.name = 'case_status' )
732 LEFT JOIN civicrm_option_value case_status ON ( civicrm_case.status_id = case_status.value
733 AND option_group_case_status.id = case_status.option_group_id )
734 LEFT JOIN civicrm_relationship case_relationship ON ( case_relationship.case_id = civicrm_case.id
735 AND case_relationship.contact_id_a = {$userID})
736 WHERE is_deleted = 0 AND cc.contact_id IN (SELECT id FROM civicrm_contact WHERE is_deleted <> 1)
737{$myCaseWhereClauseA} {$myGroupByClauseA}";
6a488035 738
33621c4f 739 $res = CRM_Core_DAO::executeQuery($query);
6a488035 740 while ($res->fetch()) {
8cc574cf 741 if (!empty($rows[$res->case_type]) && !empty($rows[$res->case_type][$res->case_status])) {
6a488035
TO
742 $rows[$res->case_type][$res->case_status]['count'] = $rows[$res->case_type][$res->case_status]['count'] + 1;
743 }
744 else {
fc9f05e0 745 $rows[$res->case_type][$res->case_status] = [
6a488035
TO
746 'count' => 1,
747 'url' => CRM_Utils_System::url('civicrm/case/search',
748 "reset=1&force=1&status={$res->status_id}&type={$res->case_type_id}&case_owner={$case_owner}"
749 ),
fc9f05e0 750 ];
6a488035
TO
751 }
752 }
753 $caseSummary['rows'] = array_merge($caseTypes, $rows);
754
755 return $caseSummary;
756 }
757
758 /**
d2e5d2ce 759 * Get Case roles.
6a488035 760 *
64bd5a0e
TO
761 * @param int $contactID
762 * Contact id.
763 * @param int $caseID
764 * Case id.
100fef9d 765 * @param int $relationshipID
0fd841b0 766 * @param bool $activeOnly
77b97be7 767 *
a6c01b45
CW
768 * @return array
769 * case role / relationships
6a488035 770 *
6a488035 771 */
0fd841b0 772 public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL, $activeOnly = TRUE) {
6a488035 773 $query = '
3b1c37fe
CW
774 SELECT rel.id as civicrm_relationship_id,
775 con.sort_name as sort_name,
6a488035
TO
776 civicrm_email.email as email,
777 civicrm_phone.phone as phone,
3b1c37fe
CW
778 con.id as civicrm_contact_id,
779 IF(rel.contact_id_a = %1, civicrm_relationship_type.label_a_b, civicrm_relationship_type.label_b_a) as relation,
780 civicrm_relationship_type.id as relation_type,
781 IF(rel.contact_id_a = %1, "a_b", "b_a") as relationship_direction
782 FROM civicrm_relationship rel
783 INNER JOIN civicrm_relationship_type ON rel.relationship_type_id = civicrm_relationship_type.id
211fe764 784 INNER JOIN civicrm_contact con ON ((con.id <> %1 AND con.id IN (rel.contact_id_a, rel.contact_id_b)) OR (con.id = %1 AND rel.contact_id_b = rel.contact_id_a AND rel.contact_id_a = %1 AND rel.is_active))
3b1c37fe
CW
785 LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = con.id AND civicrm_phone.is_primary = 1)
786 LEFT JOIN civicrm_email ON (civicrm_email.contact_id = con.id AND civicrm_email.is_primary = 1)
787 WHERE (rel.contact_id_a = %1 OR rel.contact_id_b = %1) AND rel.case_id = %2
0fd841b0
JP
788 AND con.is_deleted = 0';
789
790 if ($activeOnly) {
791 $query .= ' AND rel.is_active = 1 AND (rel.end_date IS NULL OR rel.end_date > NOW())';
792 }
6a488035 793
fc9f05e0 794 $params = [
795 1 => [$contactID, 'Positive'],
796 2 => [$caseID, 'Positive'],
797 ];
6a488035
TO
798
799 if ($relationshipID) {
a4bac981 800 $query .= ' AND rel.id = %3 ';
fc9f05e0 801 $params[3] = [$relationshipID, 'Integer'];
6a488035
TO
802 }
803 $dao = CRM_Core_DAO::executeQuery($query, $params);
804
fc9f05e0 805 $values = [];
6a488035
TO
806 while ($dao->fetch()) {
807 $rid = $dao->civicrm_relationship_id;
808 $values[$rid]['cid'] = $dao->civicrm_contact_id;
809 $values[$rid]['relation'] = $dao->relation;
810 $values[$rid]['name'] = $dao->sort_name;
811 $values[$rid]['email'] = $dao->email;
812 $values[$rid]['phone'] = $dao->phone;
813 $values[$rid]['relation_type'] = $dao->relation_type;
814 $values[$rid]['rel_id'] = $dao->civicrm_relationship_id;
3b1c37fe
CW
815 $values[$rid]['client_id'] = $contactID;
816 $values[$rid]['relationship_direction'] = $dao->relationship_direction;
6a488035
TO
817 }
818
6a488035
TO
819 return $values;
820 }
821
822 /**
d2e5d2ce 823 * Get Case Activities.
6a488035 824 *
64bd5a0e
TO
825 * @param int $caseID
826 * Case id.
827 * @param array $params
828 * Posted params.
829 * @param int $contactID
830 * Contact id.
6a488035 831 *
77b97be7 832 * @param null $context
100fef9d 833 * @param int $userID
359d4010 834 * @param null $type (deprecated)
77b97be7 835 *
a6c01b45 836 * @return array
16b10e64 837 * Array of case activities
6a488035 838 *
6a488035 839 */
00be9182 840 public static function getCaseActivity($caseID, &$params, $contactID, $context = NULL, $userID = NULL, $type = NULL) {
44f817d4 841 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
9e74e3ce 842 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
e96f025a 843 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
034500d4 844 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
9e74e3ce 845
6a488035
TO
846 // CRM-5081 - formatting the dates to omit seconds.
847 // Note the 00 in the date format string is needed otherwise later on it thinks scheduled ones are overdue.
ad280fb6 848 $select = "
3636b520 849 SELECT SQL_CALC_FOUND_ROWS COUNT(ca.id) AS ismultiple,
850 ca.id AS id,
851 ca.activity_type_id AS type,
852 ca.activity_type_id AS activity_type_id,
853 tcc.sort_name AS target_contact_name,
854 tcc.id AS target_contact_id,
855 scc.sort_name AS source_contact_name,
856 scc.id AS source_contact_id,
857 acc.sort_name AS assignee_contact_name,
858 acc.id AS assignee_contact_id,
859 DATE_FORMAT(
860 IF(ca.activity_date_time < NOW() AND ca.status_id=ov.value,
861 ca.activity_date_time,
862 DATE_ADD(NOW(), INTERVAL 1 YEAR)
863 ), '%Y%m%d%H%i00') AS overdue_date,
864 DATE_FORMAT(ca.activity_date_time, '%Y%m%d%H%i00') AS display_date,
865 ca.status_id AS status,
866 ca.subject AS subject,
867 ca.is_deleted AS deleted,
868 ca.priority_id AS priority,
869 ca.weight AS weight,
ad280fb6 870 GROUP_CONCAT(ef.file_id) AS attachment_ids ";
6a488035 871
e96f025a 872 $from = "
ad280fb6
JL
873 FROM civicrm_case_activity cca
874 INNER JOIN civicrm_activity ca
875 ON ca.id = cca.activity_id
876 INNER JOIN civicrm_activity_contact cas
877 ON cas.activity_id = ca.id
878 AND cas.record_type_id = {$sourceID}
879 INNER JOIN civicrm_contact scc
880 ON scc.id = cas.contact_id
881 LEFT JOIN civicrm_activity_contact caa
882 ON caa.activity_id = ca.id
883 AND caa.record_type_id = {$assigneeID}
884 LEFT JOIN civicrm_contact acc
885 ON acc.id = caa.contact_id
886 LEFT JOIN civicrm_activity_contact cat
887 ON cat.activity_id = ca.id
888 AND cat.record_type_id = {$targetID}
889 LEFT JOIN civicrm_contact tcc
890 ON tcc.id = cat.contact_id
891 INNER JOIN civicrm_option_group cog
892 ON cog.name = 'activity_type'
893 INNER JOIN civicrm_option_value cov
894 ON cov.option_group_id = cog.id
895 AND cov.value = ca.activity_type_id
896 AND cov.is_active = 1
897 LEFT JOIN civicrm_entity_file ef
898 ON ef.entity_table = 'civicrm_activity'
899 AND ef.entity_id = ca.id
900 LEFT OUTER JOIN civicrm_option_group og
901 ON og.name = 'activity_status'
902 LEFT OUTER JOIN civicrm_option_value ov
903 ON ov.option_group_id=og.id
904 AND ov.name = 'Scheduled'";
905
906 $where = '
907 WHERE cca.case_id= %1
908 AND ca.is_current_revision = 1';
909
910 if (!empty($params['source_contact_id'])) {
911 $where .= "
912 AND cas.contact_id = " . CRM_Utils_Type::escape($params['source_contact_id'], 'Integer');
6a488035
TO
913 }
914
a7488080 915 if (!empty($params['status_id'])) {
ad280fb6
JL
916 $where .= "
917 AND ca.status_id = " . CRM_Utils_Type::escape($params['status_id'], 'Integer');
6a488035
TO
918 }
919
a7488080 920 if (!empty($params['activity_deleted'])) {
ad280fb6
JL
921 $where .= "
922 AND ca.is_deleted = 1";
6a488035
TO
923 }
924 else {
ad280fb6
JL
925 $where .= "
926 AND ca.is_deleted = 0";
6a488035
TO
927 }
928
a7488080 929 if (!empty($params['activity_type_id'])) {
ad280fb6
JL
930 $where .= "
931 AND ca.activity_type_id = " . CRM_Utils_Type::escape($params['activity_type_id'], 'Integer');
6a488035
TO
932 }
933
a7488080 934 if (!empty($params['activity_date_low'])) {
6a488035
TO
935 $fromActivityDate = CRM_Utils_Type::escape(CRM_Utils_Date::processDate($params['activity_date_low']), 'Date');
936 }
ad280fb6
JL
937 if (!empty($fromActivityDate)) {
938 $where .= "
939 AND ca.activity_date_time >= '{$fromActivityDate}'";
940 }
941
a7488080 942 if (!empty($params['activity_date_high'])) {
6a488035
TO
943 $toActivityDate = CRM_Utils_Type::escape(CRM_Utils_Date::processDate($params['activity_date_high']), 'Date');
944 $toActivityDate = $toActivityDate ? $toActivityDate + 235959 : NULL;
945 }
6a488035 946 if (!empty($toActivityDate)) {
ad280fb6
JL
947 $where .= "
948 AND ca.activity_date_time <= '{$toActivityDate}'";
6a488035
TO
949 }
950
3636b520 951 $groupBy = "
952 GROUP BY ca.id, tcc.id, scc.id, acc.id, ov.value";
6a488035 953
9c1bc317 954 $sortBy = $params['sortBy'] ?? NULL;
ad280fb6 955 if (!$sortBy) {
6a488035 956 // CRM-5081 - added id to act like creation date
ad280fb6
JL
957 $orderBy = "
958 ORDER BY overdue_date ASC, display_date DESC, weight DESC";
6a488035
TO
959 }
960 else {
ad280fb6
JL
961 $sortBy = CRM_Utils_Type::escape($sortBy, 'String');
962 $orderBy = " ORDER BY $sortBy ";
6a488035
TO
963 }
964
9c1bc317
CW
965 $page = $params['page'] ?? NULL;
966 $rp = $params['rp'] ?? NULL;
3fff685f 967
6a488035 968 if (!$page) {
6a488035 969 $page = 1;
6a488035
TO
970 }
971 if (!$rp) {
972 $rp = 10;
973 }
6a488035 974 $start = (($page - 1) * $rp);
ad280fb6 975 $limit = " LIMIT $start, $rp";
6a488035 976
ad280fb6 977 $query = $select . $from . $where . $groupBy . $orderBy . $limit;
fc9f05e0 978 $queryParams = [1 => [$caseID, 'Integer']];
6a488035 979
3fff685f 980 $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
24963ae3 981 $caseCount = CRM_Core_DAO::singleValueQuery('SELECT FOUND_ROWS()');
ad280fb6 982
e96f025a 983 $activityTypes = CRM_Case_PseudoConstant::caseActivityType(FALSE, TRUE);
6a488035 984
0dba8ce1
JP
985 $compStatusValues = array_keys(
986 CRM_Activity_BAO_Activity::getStatusesByType(CRM_Activity_BAO_Activity::COMPLETED) +
987 CRM_Activity_BAO_Activity::getStatusesByType(CRM_Activity_BAO_Activity::CANCELLED)
988 );
ad280fb6 989
6a488035 990 if (!$userID) {
830bab40 991 $userID = CRM_Core_Session::getLoggedInContactID();
6a488035
TO
992 }
993
830bab40 994 $caseActivities = [];
ad280fb6 995
6a488035 996 while ($dao->fetch()) {
ad280fb6 997 $caseActivityId = $dao->id;
6a488035 998
830bab40
MWMC
999 //Do we have permission to access given case activity record.
1000 if (!self::checkPermission($caseActivityId, 'view', $dao->activity_type_id, $userID)) {
6a488035
TO
1001 continue;
1002 }
1003
359d4010 1004 $caseActivities[$caseActivityId]['DT_RowId'] = $caseActivityId;
ad280fb6 1005 //Add classes to the row, via DataTables syntax
359d4010 1006 $caseActivities[$caseActivityId]['DT_RowClass'] = "crm-entity status-id-$dao->status";
6a488035 1007
ad280fb6 1008 if (CRM_Utils_Array::crmInArray($dao->status, $compStatusValues)) {
359d4010 1009 $caseActivities[$caseActivityId]['DT_RowClass'] .= " status-completed";
6a488035 1010 }
ad280fb6
JL
1011 else {
1012 if (CRM_Utils_Date::overdue($dao->display_date)) {
359d4010 1013 $caseActivities[$caseActivityId]['DT_RowClass'] .= " status-overdue";
ad280fb6
JL
1014 }
1015 else {
359d4010 1016 $caseActivities[$caseActivityId]['DT_RowClass'] .= " status-scheduled";
6a488035
TO
1017 }
1018 }
ad280fb6
JL
1019
1020 if (!empty($dao->priority)) {
0571e734 1021 if ($dao->priority == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Urgent')) {
359d4010 1022 $caseActivities[$caseActivityId]['DT_RowClass'] .= " priority-urgent ";
ad280fb6 1023 }
0571e734 1024 elseif ($dao->priority == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Low')) {
359d4010 1025 $caseActivities[$caseActivityId]['DT_RowClass'] .= " priority-low ";
6a488035 1026 }
6a488035 1027 }
6a488035 1028
ad280fb6 1029 //Add data to the row for inline editing, via DataTable syntax
fc9f05e0 1030 $caseActivities[$caseActivityId]['DT_RowAttr'] = [];
359d4010
MW
1031 $caseActivities[$caseActivityId]['DT_RowAttr']['data-entity'] = 'activity';
1032 $caseActivities[$caseActivityId]['DT_RowAttr']['data-id'] = $caseActivityId;
6a488035 1033
ad280fb6 1034 //Activity Date and Time
359d4010 1035 $caseActivities[$caseActivityId]['activity_date_time'] = CRM_Utils_Date::customFormat($dao->display_date);
6a488035 1036
ad280fb6 1037 //Activity Subject
359d4010 1038 $caseActivities[$caseActivityId]['subject'] = $dao->subject;
ad280fb6
JL
1039
1040 //Activity Type
359d4010 1041 $caseActivities[$caseActivityId]['type'] = (!empty($activityTypes[$dao->type]['icon']) ? '<span class="crm-i ' . $activityTypes[$dao->type]['icon'] . '"></span> ' : '')
8c99c0bb 1042 . $activityTypes[$dao->type]['label'];
ad280fb6 1043
359d4010
MW
1044 // Activity Target (With Contact) (There can be more than one)
1045 $targetContact = self::formatContactLink($dao->target_contact_id, $dao->target_contact_name);
1046 if (empty($caseActivities[$caseActivityId]['target_contact_name'])) {
1047 $caseActivities[$caseActivityId]['target_contact_name'] = $targetContact;
1048 }
1049 else {
1050 if (strpos($caseActivities[$caseActivityId]['target_contact_name'], $targetContact) === FALSE) {
1051 $caseActivities[$caseActivityId]['target_contact_name'] .= '; ' . $targetContact;
6a488035 1052 }
ad280fb6 1053 }
ad280fb6 1054
359d4010
MW
1055 // Activity Source Contact (Reporter) (There can only be one)
1056 $sourceContact = self::formatContactLink($dao->source_contact_id, $dao->source_contact_name);
1057 $caseActivities[$caseActivityId]['source_contact_name'] = $sourceContact;
1058
1059 // Activity Assignee (There can be more than one)
1060 $assigneeContact = self::formatContactLink($dao->assignee_contact_id, $dao->assignee_contact_name);
1061 if (empty($caseActivities[$caseActivityId]['assignee_contact_name'])) {
1062 $caseActivities[$caseActivityId]['assignee_contact_name'] = $assigneeContact;
ad280fb6 1063 }
359d4010
MW
1064 else {
1065 if (strpos($caseActivities[$caseActivityId]['assignee_contact_name'], $assigneeContact) === FALSE) {
1066 $caseActivities[$caseActivityId]['assignee_contact_name'] .= '; ' . $assigneeContact;
6a488035
TO
1067 }
1068 }
ad280fb6 1069
b864360d 1070 // Activity Status Label for Case activities list
c1c1b937
MWMC
1071 $deleted = '';
1072 if ($dao->deleted) {
1073 $deleted = '<br /> ' . ts('(deleted)');
1074 }
1075 $caseActivities[$caseActivityId]['status_id'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_status_id', $dao->status) . $deleted;
1076 // if there are file attachments we will return how many
1077 if (!empty($dao->attachment_ids)) {
1078 $attachmentIDs = array_unique(explode(',', $dao->attachment_ids));
1079 $caseActivity['no_attachments'] = count($attachmentIDs);
1080 }
ad280fb6 1081
7bf3b68c
MWMC
1082 $caseActivities[$caseActivityId]['links']
1083 = CRM_Case_Selector_Search::addCaseActivityLinks($caseID, $contactID, $userID, $context, $dao);
6a488035 1084 }
6a488035 1085
fc9f05e0 1086 $caseActivitiesDT = [];
359d4010 1087 $caseActivitiesDT['data'] = array_values($caseActivities);
3fff685f
JL
1088 $caseActivitiesDT['recordsTotal'] = $caseCount;
1089 $caseActivitiesDT['recordsFiltered'] = $caseCount;
ad280fb6
JL
1090
1091 return $caseActivitiesDT;
6a488035
TO
1092 }
1093
359d4010
MW
1094 /**
1095 * Helper function to generate a formatted contact link/name for display in the Case activities tab
1096 *
1097 * @param $contactId
1098 * @param $contactName
1099 *
1100 * @return string
1101 */
1102 private static function formatContactLink($contactId, $contactName) {
1103 if (empty($contactId)) {
1104 return NULL;
1105 }
1106
1107 $hasViewContact = CRM_Contact_BAO_Contact_Permission::allow($contactId);
1108
1109 if ($hasViewContact) {
1110 $contactViewUrl = CRM_Utils_System::url("civicrm/contact/view", "reset=1&cid={$contactId}");
1111 return "<a href=\"{$contactViewUrl}\">" . $contactName . "</a>";
1112 }
1113 else {
1114 return $contactName;
1115 }
1116 }
1117
6a488035 1118 /**
d2e5d2ce 1119 * Get Case Related Contacts.
6a488035 1120 *
64bd5a0e
TO
1121 * @param int $caseID
1122 * Case id.
b982dca0 1123 * @param bool $includeDetails
64bd5a0e 1124 * If true include details of contacts.
6a488035 1125 *
a6c01b45
CW
1126 * @return array
1127 * array of return properties
6a488035 1128 *
6a488035 1129 */
b982dca0 1130 public static function getRelatedContacts($caseID, $includeDetails = TRUE) {
fc9f05e0 1131 $caseRoles = [];
b982dca0 1132 if ($includeDetails) {
fc9f05e0 1133 $caseInfo = civicrm_api3('Case', 'getsingle', [
69f9c562 1134 'id' => $caseID,
b982dca0 1135 // Most efficient way of retrieving definition is to also include case type id and name so the api doesn't have to look it up separately
fc9f05e0 1136 'return' => ['case_type_id', 'case_type_id.name', 'case_type_id.definition', 'contact_id'],
1137 ]);
69f9c562
CW
1138 if (!empty($caseInfo['case_type_id.definition']['caseRoles'])) {
1139 $caseRoles = CRM_Utils_Array::rekey($caseInfo['case_type_id.definition']['caseRoles'], 'name');
1140 }
1141 }
6a488035 1142
fc9f05e0 1143 $values = [];
d32a853d 1144 $caseClientCondition = !empty($caseInfo['client_id']) ? "AND cc.id NOT IN (%2)" : '';
41cf58d3 1145 $query = <<<HERESQL
c50e15c8 1146 SELECT cc.display_name as name, cc.sort_name as sort_name, cc.id, cr.relationship_type_id, crt.label_b_a as role, crt.name_b_a as role_name, crt.name_a_b as role_name_reverse, ce.email, cp.phone
41cf58d3
AF
1147 FROM civicrm_relationship cr
1148 JOIN civicrm_relationship_type crt
1149 ON crt.id = cr.relationship_type_id
1150 JOIN civicrm_contact cc
1151 ON cc.id = cr.contact_id_a
1152 AND cc.is_deleted <> 1
1153 LEFT JOIN civicrm_email ce
1154 ON ce.contact_id = cc.id
1155 AND ce.is_primary= 1
1156 LEFT JOIN civicrm_phone cp
1157 ON cp.contact_id = cc.id
1158 AND cp.is_primary= 1
1159 WHERE cr.case_id = %1
1160 AND cr.is_active
d32a853d 1161 {$caseClientCondition}
41cf58d3 1162 UNION
c50e15c8 1163 SELECT cc.display_name as name, cc.sort_name as sort_name, cc.id, cr.relationship_type_id, crt.label_a_b as role, crt.name_a_b as role_name, crt.name_b_a as role_name_reverse, ce.email, cp.phone
41cf58d3
AF
1164 FROM civicrm_relationship cr
1165 JOIN civicrm_relationship_type crt
1166 ON crt.id = cr.relationship_type_id
1167 JOIN civicrm_contact cc
1168 ON cc.id = cr.contact_id_b
1169 AND cc.is_deleted <> 1
1170 LEFT JOIN civicrm_email ce
1171 ON ce.contact_id = cc.id
1172 AND ce.is_primary= 1
1173 LEFT JOIN civicrm_phone cp
1174 ON cp.contact_id = cc.id
1175 AND cp.is_primary= 1
1176 WHERE cr.case_id = %1
1177 AND cr.is_active
d32a853d 1178 {$caseClientCondition}
41cf58d3 1179HERESQL;
9cf08be4 1180
fc9f05e0 1181 $params = [
1182 1 => [$caseID, 'Integer'],
fc9f05e0 1183 ];
d32a853d
TO
1184
1185 if ($caseClientCondition) {
1186 $params[2] = [implode(',', $caseInfo['client_id']), 'CommaSeparatedIntegers'];
1187 }
6a488035
TO
1188 $dao = CRM_Core_DAO::executeQuery($query, $params);
1189
1190 while ($dao->fetch()) {
b982dca0 1191 if (!$includeDetails) {
6a488035
TO
1192 $values[$dao->id] = 1;
1193 }
1194 else {
fc9f05e0 1195 $details = [
6a488035
TO
1196 'contact_id' => $dao->id,
1197 'display_name' => $dao->name,
1198 'sort_name' => $dao->sort_name,
69f9c562 1199 'relationship_type_id' => $dao->relationship_type_id,
6a488035
TO
1200 'role' => $dao->role,
1201 'email' => $dao->email,
a0c7081b 1202 'phone' => $dao->phone,
fc9f05e0 1203 ];
b982dca0 1204 // Add more info about the role (creator, manager)
c50e15c8 1205 // The XML historically has the reverse direction, so look up reverse.
9c1bc317 1206 $role = $caseRoles[$dao->role_name_reverse] ?? NULL;
69f9c562
CW
1207 if ($role) {
1208 unset($role['name']);
1209 $details += $role;
1210 }
1211 $values[] = $details;
6a488035
TO
1212 }
1213 }
6a488035
TO
1214
1215 return $values;
1216 }
1217
1218 /**
100fef9d 1219 * Send e-mail copy of activity
6a488035 1220 *
100fef9d 1221 * @param int $clientId
64bd5a0e
TO
1222 * @param int $activityId
1223 * Activity Id.
1224 * @param array $contacts
1225 * Array of related contact.
6a488035 1226 *
77b97be7 1227 * @param null $attachments
100fef9d 1228 * @param int $caseId
77b97be7 1229 *
67d19299 1230 * @return bool |array
6a488035 1231 */
00be9182 1232 public static function sendActivityCopy($clientId, $activityId, $contacts, $attachments = NULL, $caseId) {
6a488035 1233 if (!$activityId) {
67d19299 1234 return FALSE;
6a488035
TO
1235 }
1236
fc9f05e0 1237 $tplParams = $activityInfo = [];
8d04ae48
AP
1238 $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, 'activity_type_id');
1239 // If it's a case activity
6a488035 1240 if ($caseId) {
6a488035 1241 $nonCaseActivityTypes = CRM_Core_PseudoConstant::activityType();
a7488080 1242 if (!empty($nonCaseActivityTypes[$activityTypeId])) {
6a488035
TO
1243 $anyActivity = TRUE;
1244 }
1245 else {
1246 $anyActivity = FALSE;
1247 }
1248 $tplParams['isCaseActivity'] = 1;
1249 $tplParams['client_id'] = $clientId;
1250 }
1251 else {
1252 $anyActivity = TRUE;
1253 }
1254
1255 $xmlProcessorProcess = new CRM_Case_XMLProcessor_Process();
1256 $isRedact = $xmlProcessorProcess->getRedactActivityEmail();
1257
1258 $xmlProcessorReport = new CRM_Case_XMLProcessor_Report();
1259
1260 $activityInfo = $xmlProcessorReport->getActivityInfo($clientId, $activityId, $anyActivity, $isRedact);
1261 if ($caseId) {
fc9f05e0 1262 $activityInfo['fields'][] = ['label' => 'Case ID', 'type' => 'String', 'value' => $caseId];
6a488035 1263 }
8d04ae48 1264 $tplParams['activityTypeName'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_DAO_Activity', 'activity_type_id', $activityTypeId);
6a488035
TO
1265 $tplParams['activity'] = $activityInfo;
1266 foreach ($tplParams['activity']['fields'] as $k => $val) {
1267 if (CRM_Utils_Array::value('label', $val) == ts('Subject')) {
1268 $activitySubject = $val['value'];
1269 break;
1270 }
1271 }
f0c3f9bc 1272
6a488035 1273 // CRM-8926 If user is not logged in, use the activity creator as userID
f0c3f9bc 1274 if (!($userID = CRM_Core_Session::getLoggedInContactID())) {
4322672b 1275 $userID = CRM_Activity_BAO_Activity::getSourceContactID($activityId);
6a488035
TO
1276 }
1277
1278 //also create activities simultaneously of this copy.
fc9f05e0 1279 $activityParams = [];
6a488035
TO
1280
1281 $activityParams['source_record_id'] = $activityId;
1282 $activityParams['source_contact_id'] = $userID;
95a718e1 1283 $activityParams['activity_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_type_id', 'Email');
6a488035 1284 $activityParams['activity_date_time'] = date('YmdHis');
95a718e1
JP
1285 $activityParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_status_id', 'Completed');
1286 $activityParams['medium_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'encounter_medium', 'email');
6a488035
TO
1287 $activityParams['case_id'] = $caseId;
1288 $activityParams['is_auto'] = 0;
1289 $activityParams['target_id'] = $clientId;
1290
1291 $tplParams['activitySubject'] = $activitySubject;
1292
1293 // if it’s a case activity, add hashed id to the template (CRM-5916)
1294 if ($caseId) {
1295 $tplParams['idHash'] = substr(sha1(CIVICRM_SITE_KEY . $caseId), 0, 7);
1296 }
1297
fc9f05e0 1298 $result = [];
8aeeea00
EH
1299 // CRM-20308 get receiptFrom defaults see https://issues.civicrm.org/jira/browse/CRM-20308
1300 $receiptFrom = self::getReceiptFrom($activityId);
6a488035 1301
fc9f05e0 1302 $recordedActivityParams = [];
6a488035
TO
1303
1304 foreach ($contacts as $mail => $info) {
1305 $tplParams['contact'] = $info;
1306 self::buildPermissionLinks($tplParams, $activityParams);
1307
9c1bc317 1308 $displayName = $info['display_name'] ?? NULL;
6a488035 1309
c6327d7d 1310 list($result[CRM_Utils_Array::value('contact_id', $info)], $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate(
fc9f05e0 1311 [
6a488035
TO
1312 'groupName' => 'msg_tpl_workflow_case',
1313 'valueName' => 'case_activity',
6b409353 1314 'contactId' => $info['contact_id'] ?? NULL,
6a488035
TO
1315 'tplParams' => $tplParams,
1316 'from' => $receiptFrom,
1317 'toName' => $displayName,
1318 'toEmail' => $mail,
1319 'attachments' => $attachments,
fc9f05e0 1320 ]
6a488035
TO
1321 );
1322
898f01ea 1323 $activityParams['subject'] = ts('%1 - copy sent to %2', [1 => $activitySubject, 2 => $displayName]);
6a488035
TO
1324 $activityParams['details'] = $message;
1325
21827a41 1326 if (!empty($result[$info['contact_id']])) {
6a488035
TO
1327 /*
1328 * Really only need to record one activity with all the targets combined.
1329 * Originally the template was going to possibly have different content, e.g. depending on permissions,
1330 * but it's always the same content at the moment.
1331 */
1332 if (empty($recordedActivityParams)) {
1333 $recordedActivityParams = $activityParams;
1334 }
1335 else {
1336 $recordedActivityParams['subject'] .= "; $displayName";
1337 }
1338 $recordedActivityParams['target_contact_id'][] = $info['contact_id'];
1339 }
1340 else {
21827a41 1341 unset($result[CRM_Utils_Array::value('contact_id', $info)]);
6a488035
TO
1342 }
1343 }
1344
1345 if (!empty($recordedActivityParams)) {
1346 $activity = CRM_Activity_BAO_Activity::create($recordedActivityParams);
1347
1348 //create case_activity record if its case activity.
1349 if ($caseId) {
fc9f05e0 1350 $caseParams = [
6a488035
TO
1351 'activity_id' => $activity->id,
1352 'case_id' => $caseId,
fc9f05e0 1353 ];
6a488035
TO
1354 self::processCaseActivity($caseParams);
1355 }
1356 }
1357
1358 return $result;
1359 }
1360
1361 /**
1362 * Retrieve count of activities having a particular type, and
1363 * associated with a particular case.
1364 *
64bd5a0e
TO
1365 * @param int $caseId
1366 * ID of the case.
1367 * @param int $activityTypeId
1368 * ID of the activity type.
6a488035
TO
1369 *
1370 * @return array
6a488035 1371 */
00be9182 1372 public static function getCaseActivityCount($caseId, $activityTypeId) {
fc9f05e0 1373 $queryParam = [
1374 1 => [$caseId, 'Integer'],
1375 2 => [$activityTypeId, 'Integer'],
1376 ];
6a488035
TO
1377 $query = "SELECT count(ca.id) as countact
1378 FROM civicrm_activity ca
1379 INNER JOIN civicrm_case_activity cca ON ca.id = cca.activity_id
1380 WHERE ca.activity_type_id = %2
1381 AND cca.case_id = %1
1382 AND ca.is_deleted = 0";
1383
1384 $dao = CRM_Core_DAO::executeQuery($query, $queryParam);
1385 if ($dao->fetch()) {
1386 return $dao->countact;
1387 }
1388
1389 return FALSE;
1390 }
1391
6a488035 1392 /**
d2e5d2ce 1393 * Retrieve the scheduled activity type and date.
6a488035 1394 *
64bd5a0e
TO
1395 * @param array $cases
1396 * Array of contact and case id.
77b97be7
EM
1397 *
1398 * @param string $type
6a488035 1399 *
a6c01b45
CW
1400 * @return array
1401 * Array of scheduled activity type and date
6a488035 1402 *
6a488035 1403 *
6a488035 1404 */
00be9182 1405 public static function getNextScheduledActivity($cases, $type = 'upcoming') {
f0c3f9bc 1406 $userID = CRM_Core_Session::getLoggedInContactID();
6a488035
TO
1407
1408 $caseID = implode(',', $cases['case_id']);
1409 $contactID = implode(',', $cases['contact_id']);
1410
5f1c8c57 1411 $condition = " civicrm_case_contact.contact_id IN( {$contactID} )
6a488035
TO
1412 AND civicrm_case.id IN( {$caseID})
1413 AND civicrm_case.is_deleted = {$cases['case_deleted']}";
1414
2ff620ae 1415 $query = self::getCaseActivityQuery($type, $userID, $condition);
57d38398 1416 $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id');
6a488035 1417
33621c4f 1418 $res = CRM_Core_DAO::executeQuery($query);
6a488035 1419
fc9f05e0 1420 $activityInfo = [];
6a488035
TO
1421 while ($res->fetch()) {
1422 if ($type == 'upcoming') {
2c783cce 1423 $activityInfo[$res->case_id]['date'] = $res->activity_date_time;
9c1bc317 1424 $activityInfo[$res->case_id]['type'] = $activityTypes[$res->activity_type_id] ?? NULL;
6a488035
TO
1425 }
1426 else {
2c783cce 1427 $activityInfo[$res->case_id]['date'] = $res->activity_date_time;
9c1bc317 1428 $activityInfo[$res->case_id]['type'] = $activityTypes[$res->activity_type_id] ?? NULL;
6a488035
TO
1429 }
1430 }
1431
1432 return $activityInfo;
1433 }
1434
1435 /**
d2e5d2ce 1436 * Combine all the exportable fields from the lower levels object.
6a488035 1437 *
a6c01b45
CW
1438 * @return array
1439 * array of exportable Fields
6a488035 1440 */
00be9182 1441 public static function &exportableFields() {
6a488035
TO
1442 if (!self::$_exportableFields) {
1443 if (!self::$_exportableFields) {
fc9f05e0 1444 self::$_exportableFields = [];
6a488035
TO
1445 }
1446
e96f025a 1447 $fields = CRM_Case_DAO_Case::export();
fc9f05e0 1448 $fields['case_role'] = ['title' => ts('Role in Case')];
1449 $fields['case_type'] = [
e96f025a 1450 'title' => ts('Case Type'),
6a488035 1451 'name' => 'case_type',
fc9f05e0 1452 ];
1453 $fields['case_status'] = [
e96f025a 1454 'title' => ts('Case Status'),
6a488035 1455 'name' => 'case_status',
fc9f05e0 1456 ];
6a488035 1457
e75182f2
JJ
1458 // add custom data for cases
1459 $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Case'));
1460
6a488035
TO
1461 self::$_exportableFields = $fields;
1462 }
1463 return self::$_exportableFields;
1464 }
1465
1466 /**
d2e5d2ce 1467 * Restore the record that are associated with this case.
6a488035 1468 *
64bd5a0e
TO
1469 * @param int $caseId
1470 * Id of the case to restore.
6a488035 1471 *
72b3a70c 1472 * @return bool
6a488035 1473 */
00be9182 1474 public static function restoreCase($caseId) {
6a488035
TO
1475 //restore activities
1476 $activities = self::getCaseActivityDates($caseId);
1477 if ($activities) {
1478 foreach ($activities as $value) {
1479 CRM_Activity_BAO_Activity::restoreActivity($value);
1480 }
1481 }
1482 //restore case
e96f025a 1483 $case = new CRM_Case_DAO_Case();
1484 $case->id = $caseId;
6a488035
TO
1485 $case->is_deleted = 0;
1486 $case->save();
1487
1488 //CRM-7364, enable relationships
1489 self::enableDisableCaseRelationships($caseId, TRUE);
1490 return TRUE;
1491 }
1492
4c6ce474
EM
1493 /**
1494 * @param $groupInfo
1495 * @param null $sort
1496 * @param null $showLinks
1497 * @param bool $returnOnlyCount
1498 * @param int $offset
1499 * @param int $rowCount
1500 *
1501 * @return array
1502 */
00be9182 1503 public static function getGlobalContacts(&$groupInfo, $sort = NULL, $showLinks = NULL, $returnOnlyCount = FALSE, $offset = 0, $rowCount = 25) {
fc9f05e0 1504 $globalContacts = [];
6a488035
TO
1505
1506 $settingsProcessor = new CRM_Case_XMLProcessor_Settings();
1507 $settings = $settingsProcessor->run();
1508 if (!empty($settings)) {
1509 $groupInfo['name'] = $settings['groupname'];
1510 if ($groupInfo['name']) {
fc9f05e0 1511 $searchParams = ['name' => $groupInfo['name']];
1512 $results = [];
6a488035
TO
1513 CRM_Contact_BAO_Group::retrieve($searchParams, $results);
1514 if ($results) {
e96f025a 1515 $groupInfo['id'] = $results['id'];
6a488035 1516 $groupInfo['title'] = $results['title'];
fc9f05e0 1517 $params = [['group', '=', $groupInfo['id'], 0, 0]];
1518 $return = ['contact_id' => 1, 'sort_name' => 1, 'display_name' => 1, 'email' => 1, 'phone' => 1];
f9ff6700 1519 list($globalContacts) = CRM_Contact_BAO_Query::apiQuery($params, $return, NULL, $sort, $offset, $rowCount, TRUE, $returnOnlyCount, FALSE);
6a488035
TO
1520
1521 if ($returnOnlyCount) {
1522 return $globalContacts;
1523 }
1524
1525 if ($showLinks) {
e96f025a 1526 foreach ($globalContacts as $idx => $contact) {
d79c94d5 1527 $globalContacts[$idx]['sort_name'] = '<a href="' . CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$contact['contact_id']}") . '">' . $contact['sort_name'] . '</a>';
6a488035
TO
1528 }
1529 }
1530 }
1531 }
1532 }
1533 return $globalContacts;
1534 }
1535
4c6ce474 1536 /**
d2e5d2ce 1537 * Convenience function to get both case contacts and global in one array.
fc9f05e0 1538 *
100fef9d 1539 * @param int $caseId
4c6ce474
EM
1540 *
1541 * @return array
1542 */
00be9182 1543 public static function getRelatedAndGlobalContacts($caseId) {
6a488035
TO
1544 $relatedContacts = self::getRelatedContacts($caseId);
1545
fc9f05e0 1546 $groupInfo = [];
6a488035
TO
1547 $globalContacts = self::getGlobalContacts($groupInfo);
1548
1549 //unset values which are not required.
1550 foreach ($globalContacts as $k => & $v) {
1551 unset($v['email_id']);
1552 unset($v['group_contact_id']);
1553 unset($v['status']);
1554 unset($v['phone']);
1555 $v['role'] = $groupInfo['title'];
1556 }
1557 //include multiple listings for the same contact/different roles.
1558 $relatedGlobalContacts = array_merge($relatedContacts, $globalContacts);
1559 return $relatedGlobalContacts;
1560 }
1561
1562 /**
100fef9d 1563 * Get Case ActivitiesDueDates with given criteria.
6a488035 1564 *
64bd5a0e
TO
1565 * @param int $caseID
1566 * Case id.
1567 * @param array $criteriaParams
1568 * Given criteria.
1569 * @param bool $latestDate
72b3a70c 1570 * If set newest or oldest date is selected.
6a488035 1571 *
72b3a70c
CW
1572 * @return array
1573 * case activities due dates
6a488035 1574 *
6a488035 1575 */
fc9f05e0 1576 public static function getCaseActivityDates($caseID, $criteriaParams = [], $latestDate = FALSE) {
1577 $values = [];
6a488035 1578 $selectDate = " ca.activity_date_time";
e96f025a 1579 $where = $groupBy = ' ';
6a488035
TO
1580
1581 if (!$caseID) {
408b79bf 1582 return NULL;
6a488035
TO
1583 }
1584
1585 if ($latestDate) {
a7488080 1586 if (!empty($criteriaParams['activity_type_id'])) {
6a488035
TO
1587 $where .= " AND ca.activity_type_id = " . CRM_Utils_Type::escape($criteriaParams['activity_type_id'], 'Integer');
1588 $where .= " AND ca.is_current_revision = 1";
e5cceea5 1589 $groupBy .= " GROUP BY ca.activity_type_id, ca.id";
6a488035
TO
1590 }
1591
a7488080 1592 if (!empty($criteriaParams['newest'])) {
6a488035
TO
1593 $selectDate = " max(ca.activity_date_time) ";
1594 }
1595 else {
1596 $selectDate = " min(ca.activity_date_time) ";
1597 }
1598 }
1599
1600 $query = "SELECT ca.id, {$selectDate} as activity_date
1601 FROM civicrm_activity ca
1602 LEFT JOIN civicrm_case_activity cca ON cca.activity_id = ca.id LEFT JOIN civicrm_case cc ON cc.id = cca.case_id
1603 WHERE cc.id = %1 {$where} {$groupBy}";
1604
fc9f05e0 1605 $params = [1 => [$caseID, 'Integer']];
6a488035
TO
1606 $dao = CRM_Core_DAO::executeQuery($query, $params);
1607
1608 while ($dao->fetch()) {
1609 $values[$dao->id]['id'] = $dao->id;
1610 $values[$dao->id]['activity_date'] = $dao->activity_date;
1611 }
6a488035
TO
1612 return $values;
1613 }
1614
1615 /**
100fef9d 1616 * Create activities when Case or Other roles assigned/modified/deleted.
6a488035 1617 *
100fef9d 1618 * @param int $caseId
64bd5a0e
TO
1619 * @param int $relationshipId
1620 * Relationship id.
1621 * @param int $relContactId
1622 * Case role assignee contactId.
100fef9d 1623 * @param int $contactId
6a488035 1624 */
00be9182 1625 public static function createCaseRoleActivity($caseId, $relationshipId, $relContactId = NULL, $contactId = NULL) {
6a488035
TO
1626 if (!$caseId || !$relationshipId || empty($relationshipId)) {
1627 return;
1628 }
1629
fc9f05e0 1630 $queryParam = [];
6a488035
TO
1631 if (is_array($relationshipId)) {
1632 $relationshipId = implode(',', $relationshipId);
1633 $relationshipClause = " civicrm_relationship.id IN ($relationshipId)";
1634 }
1635 else {
1636 $relationshipClause = " civicrm_relationship.id = %1";
fc9f05e0 1637 $queryParam[1] = [$relationshipId, 'Positive'];
6a488035
TO
1638 }
1639
1640 $query = "
1641 SELECT cc.display_name as clientName,
1642 cca.display_name as assigneeContactName,
1643 civicrm_relationship.case_id as caseId,
1644 civicrm_relationship_type.label_a_b as relation_a_b,
1645 civicrm_relationship_type.label_b_a as relation_b_a,
1646 civicrm_relationship.contact_id_b as rel_contact_id,
1647 civicrm_relationship.contact_id_a as assign_contact_id
1648 FROM civicrm_relationship_type, civicrm_relationship
1649 LEFT JOIN civicrm_contact cc ON cc.id = civicrm_relationship.contact_id_b
1650 LEFT JOIN civicrm_contact cca ON cca.id = civicrm_relationship.contact_id_a
1651 WHERE civicrm_relationship.relationship_type_id = civicrm_relationship_type.id AND {$relationshipClause}";
1652
1653 $dao = CRM_Core_DAO::executeQuery($query, $queryParam);
1654
1655 while ($dao->fetch()) {
e3b9b4f6
KW
1656 // The assignee is not the client.
1657 if ($dao->rel_contact_id != $contactId) {
6a488035
TO
1658 $caseRelationship = $dao->relation_a_b;
1659 $assigneContactName = $dao->clientName;
1660 $assigneContactIds[$dao->rel_contact_id] = $dao->rel_contact_id;
1661 }
1662 else {
1663 $caseRelationship = $dao->relation_b_a;
1664 $assigneContactName = $dao->assigneeContactName;
1665 $assigneContactIds[$dao->assign_contact_id] = $dao->assign_contact_id;
1666 }
1667 }
1668
1669 $session = CRM_Core_Session::singleton();
fc9f05e0 1670 $activityParams = [
6a488035
TO
1671 'source_contact_id' => $session->get('userID'),
1672 'subject' => $caseRelationship . ' : ' . $assigneContactName,
1673 'activity_date_time' => date('YmdHis'),
9c248a42 1674 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'),
fc9f05e0 1675 ];
6a488035
TO
1676
1677 //if $relContactId is passed, role is added or modified.
1678 if (!empty($relContactId)) {
1679 $activityParams['assignee_contact_id'] = $assigneContactIds;
9c248a42 1680 $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Assign Case Role');
6a488035
TO
1681 }
1682 else {
9c248a42 1683 $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Remove Case Role');
6a488035
TO
1684 }
1685
1686 $activityParams['activity_type_id'] = $activityTypeID;
1687
1688 $activity = CRM_Activity_BAO_Activity::create($activityParams);
1689
1690 //create case_activity record.
fc9f05e0 1691 $caseParams = [
6a488035
TO
1692 'activity_id' => $activity->id,
1693 'case_id' => $caseId,
fc9f05e0 1694 ];
6a488035
TO
1695
1696 CRM_Case_BAO_Case::processCaseActivity($caseParams);
1697 }
1698
1699 /**
100fef9d 1700 * Get case manger
6a488035
TO
1701 * contact which is assigned a case role of case manager.
1702 *
64bd5a0e
TO
1703 * @param int $caseType
1704 * Case type.
1705 * @param int $caseId
1706 * Case id.
6a488035 1707 *
c00bd201 1708 * @return string
1709 * html hyperlink of manager contact view page
6a488035 1710 *
6a488035 1711 */
00be9182 1712 public static function getCaseManagerContact($caseType, $caseId) {
6a488035 1713 if (!$caseType || !$caseId) {
408b79bf 1714 return NULL;
6a488035
TO
1715 }
1716
5757b086 1717 $caseManagerName = '---';
6a488035
TO
1718 $xmlProcessor = new CRM_Case_XMLProcessor_Process();
1719
1720 $managerRoleId = $xmlProcessor->getCaseManagerRoleId($caseType);
1721
1722 if (!empty($managerRoleId)) {
41cf58d3
AF
1723 if (substr($managerRoleId, -4) == '_a_b') {
1724 $managerRoleQuery = "
1725 SELECT civicrm_contact.id as casemanager_id,
1726 civicrm_contact.sort_name as casemanager
1727 FROM civicrm_contact
1728 LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) AND civicrm_relationship.is_active
1729 LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id
1730 WHERE civicrm_case.id = %2 AND is_active = 1";
1731 }
1732 if (substr($managerRoleId, -4) == '_b_a') {
1733 $managerRoleQuery = "
1734 SELECT civicrm_contact.id as casemanager_id,
1735 civicrm_contact.sort_name as casemanager
1736 FROM civicrm_contact
1737 LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_a = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) AND civicrm_relationship.is_active
1738 LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id
1739 WHERE civicrm_case.id = %2 AND is_active = 1";
1740 }
6a488035 1741
fc9f05e0 1742 $managerRoleParams = [
1743 1 => [substr($managerRoleId, 0, -4), 'Integer'],
1744 2 => [$caseId, 'Integer'],
1745 ];
6a488035
TO
1746
1747 $dao = CRM_Core_DAO::executeQuery($managerRoleQuery, $managerRoleParams);
1748 if ($dao->fetch()) {
5757b086 1749 $caseManagerName = sprintf('<a href="%s">%s</a>',
fc9f05e0 1750 CRM_Utils_System::url('civicrm/contact/view', ['cid' => $dao->casemanager_id]),
c00bd201 1751 $dao->casemanager
1752 );
6a488035
TO
1753 }
1754 }
5757b086 1755
1756 return $caseManagerName;
6a488035
TO
1757 }
1758
4c6ce474 1759 /**
100fef9d 1760 * @param int $contactId
4c6ce474
EM
1761 * @param bool $excludeDeleted
1762 *
eeb45e43 1763 * @return int
4c6ce474 1764 */
00be9182 1765 public static function caseCount($contactId = NULL, $excludeDeleted = TRUE) {
fc9f05e0 1766 $params = ['check_permissions' => TRUE];
6a488035 1767 if ($excludeDeleted) {
0a1a8b63 1768 $params['is_deleted'] = 0;
6a488035
TO
1769 }
1770 if ($contactId) {
0a1a8b63 1771 $params['contact_id'] = $contactId;
6a488035 1772 }
eeb45e43
CW
1773 try {
1774 return civicrm_api3('Case', 'getcount', $params);
1775 }
1776 catch (CiviCRM_API3_Exception $e) {
1777 // Lack of permissions will throw an exception
1778 return 0;
1779 }
6a488035
TO
1780 }
1781
1782 /**
6e19e2ea 1783 * Retrieve related case ids for given case.
6a488035 1784 *
6e19e2ea 1785 * @param int $caseId
64bd5a0e
TO
1786 * @param bool $excludeDeleted
1787 * Do not include deleted cases.
6a488035 1788 *
72b3a70c 1789 * @return array
6a488035 1790 */
6e19e2ea 1791 public static function getRelatedCaseIds($caseId, $excludeDeleted = TRUE) {
6a488035
TO
1792 //FIXME : do check for permissions.
1793
6e19e2ea 1794 if (!$caseId) {
fc9f05e0 1795 return [];
6a488035
TO
1796 }
1797
b864360d 1798 $linkActType = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Link Cases');
6a488035 1799 if (!$linkActType) {
fc9f05e0 1800 return [];
6a488035
TO
1801 }
1802
1803 $whereClause = "mainCase.id = %2";
1804 if ($excludeDeleted) {
1805 $whereClause .= " AND ( relAct.is_deleted = 0 OR relAct.is_deleted IS NULL )";
1806 }
1807
6a488035
TO
1808 $query = "
1809 SELECT relCaseAct.case_id
1810 FROM civicrm_case mainCase
1811 INNER JOIN civicrm_case_activity mainCaseAct ON (mainCaseAct.case_id = mainCase.id)
1812 INNER JOIN civicrm_activity mainAct ON (mainCaseAct.activity_id = mainAct.id AND mainAct.activity_type_id = %1)
1813 INNER JOIN civicrm_case_activity relCaseAct ON (relCaseAct.activity_id = mainAct.id AND mainCaseAct.id != relCaseAct.id)
1814 INNER JOIN civicrm_activity relAct ON (relCaseAct.activity_id = relAct.id AND relAct.activity_type_id = %1)
1815 WHERE $whereClause";
1816
fc9f05e0 1817 $dao = CRM_Core_DAO::executeQuery($query, [
1818 1 => [$linkActType, 'Integer'],
1819 2 => [$caseId, 'Integer'],
1820 ]);
1821 $relatedCaseIds = [];
6a488035
TO
1822 while ($dao->fetch()) {
1823 $relatedCaseIds[$dao->case_id] = $dao->case_id;
1824 }
6a488035 1825
6e19e2ea
CW
1826 return array_values($relatedCaseIds);
1827 }
1828
1829 /**
1830 * Retrieve related case details for given case.
1831 *
1832 * @param int $caseId
1833 * @param bool $excludeDeleted
1834 * Do not include deleted cases.
1835 *
1836 * @return array
1837 */
1838 public static function getRelatedCases($caseId, $excludeDeleted = TRUE) {
1839 $relatedCaseIds = self::getRelatedCaseIds($caseId, $excludeDeleted);
fc9f05e0 1840 $relatedCases = [];
6e19e2ea
CW
1841
1842 if (!$relatedCaseIds) {
fc9f05e0 1843 return [];
6a488035
TO
1844 }
1845
1846 $whereClause = 'relCase.id IN ( ' . implode(',', $relatedCaseIds) . ' )';
1847 if ($excludeDeleted) {
1848 $whereClause .= " AND ( relCase.is_deleted = 0 OR relCase.is_deleted IS NULL )";
1849 }
1850
1851 //filter for permissioned cases.
fc9f05e0 1852 $filterCases = [];
6a488035
TO
1853 $doFilterCases = FALSE;
1854 if (!CRM_Core_Permission::check('access all cases and activities')) {
1855 $doFilterCases = TRUE;
5f1c8c57 1856 $filterCases = CRM_Case_BAO_Case::getCases(FALSE);
6a488035
TO
1857 }
1858
1859 //2. fetch the details of related cases.
1860 $query = "
1861 SELECT relCase.id as id,
8ffdec17 1862 civicrm_case_type.title as case_type,
6a488035 1863 client.display_name as client_name,
74b15fae
CR
1864 client.id as client_id,
1865 relCase.status_id
6a488035
TO
1866 FROM civicrm_case relCase
1867 INNER JOIN civicrm_case_contact relCaseContact ON ( relCase.id = relCaseContact.case_id )
1868 INNER JOIN civicrm_contact client ON ( client.id = relCaseContact.contact_id )
8ffdec17 1869 LEFT JOIN civicrm_case_type ON relCase.case_type_id = civicrm_case_type.id
6a488035
TO
1870 WHERE {$whereClause}";
1871
e96f025a 1872 $dao = CRM_Core_DAO::executeQuery($query);
6a488035
TO
1873 $contactViewUrl = CRM_Utils_System::url("civicrm/contact/view", "reset=1&cid=");
1874 $hasViewContact = CRM_Core_Permission::giveMeAllACLs();
74b15fae 1875 $statuses = CRM_Case_BAO_Case::buildOptions('status_id');
6a488035
TO
1876
1877 while ($dao->fetch()) {
1878 $caseView = NULL;
1879 if (!$doFilterCases || array_key_exists($dao->id, $filterCases)) {
1880 $caseViewStr = "reset=1&id={$dao->id}&cid={$dao->client_id}&action=view&context=case&selectedChild=case";
1881 $caseViewUrl = CRM_Utils_System::url("civicrm/contact/view/case", $caseViewStr);
6ce08914 1882 $caseView = "<a class='action-item no-popup crm-hover-button' href='{$caseViewUrl}'>" . ts('View Case') . "</a>";
6a488035
TO
1883 }
1884 $clientView = $dao->client_name;
1885 if ($hasViewContact) {
1886 $clientView = "<a href='{$contactViewUrl}{$dao->client_id}'>$dao->client_name</a>";
1887 }
1888
fc9f05e0 1889 $relatedCases[$dao->id] = [
6a488035
TO
1890 'case_id' => $dao->id,
1891 'case_type' => $dao->case_type,
1892 'client_name' => $clientView,
50a15c9b 1893 'status_id' => $dao->status_id,
74b15fae 1894 'case_status' => $statuses[$dao->status_id],
6a488035 1895 'links' => $caseView,
fc9f05e0 1896 ];
6a488035 1897 }
6a488035
TO
1898
1899 return $relatedCases;
1900 }
1901
1902 /**
1903 * Merge two duplicate contacts' cases - follow CRM-5758 rules.
1904 *
fc9f05e0 1905 * @param int $mainContactId
1906 * @param int $otherContactId
1907 *
6a488035
TO
1908 * @see CRM_Dedupe_Merger::cpTables()
1909 *
1910 * TODO: use the 3rd $sqls param to append sql statements rather than executing them here
cde2037d 1911 *
6a488035 1912 */
00be9182 1913 public static function mergeContacts($mainContactId, $otherContactId) {
6a488035
TO
1914 self::mergeCases($mainContactId, NULL, $otherContactId);
1915 }
1916
1917 /**
1918 * Function perform two task.
1919 * 1. Merge two duplicate contacts cases - follow CRM-5758 rules.
1920 * 2. Merge two cases of same contact - follow CRM-5598 rules.
1921 *
64bd5a0e
TO
1922 * @param int $mainContactId
1923 * Contact id of main contact record.
1924 * @param int $mainCaseId
1925 * Case id of main case record.
1926 * @param int $otherContactId
1927 * Contact id of record which is going to merge.
1928 * @param int $otherCaseId
1929 * Case id of record which is going to merge.
77b97be7
EM
1930 *
1931 * @param bool $changeClient
6a488035 1932 *
e97c66ff 1933 * @return int|null
1934 * @throws \CRM_Core_Exception
6a488035 1935 */
a130e045 1936 public static function mergeCases(
28d4d481
TO
1937 $mainContactId, $mainCaseId = NULL, $otherContactId = NULL,
1938 $otherCaseId = NULL, $changeClient = FALSE) {
6a488035
TO
1939 $moveToTrash = TRUE;
1940
1941 $duplicateContacts = FALSE;
1942 if ($mainContactId && $otherContactId &&
1943 $mainContactId != $otherContactId
1944 ) {
1945 $duplicateContacts = TRUE;
1946 }
1947
1948 $duplicateCases = FALSE;
1949 if ($mainCaseId && $otherCaseId &&
1950 $mainCaseId != $otherCaseId
1951 ) {
1952 $duplicateCases = TRUE;
1953 }
1954
fc9f05e0 1955 $mainCaseIds = [];
6a488035
TO
1956 if (!$duplicateContacts && !$duplicateCases) {
1957 return $mainCaseIds;
1958 }
1959
b864360d
MWMC
1960 $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate');
1961 $completedActivityStatus = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed');
44f817d4 1962 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
9e74e3ce 1963 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
1964 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
1965 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
8ef12e64 1966
fc9f05e0 1967 $processCaseIds = [$otherCaseId];
6a488035
TO
1968 if ($duplicateContacts && !$duplicateCases) {
1969 if ($changeClient) {
fc9f05e0 1970 $processCaseIds = [$mainCaseId];
6a488035
TO
1971 }
1972 else {
1973 //get all case ids for other contact.
1974 $processCaseIds = self::retrieveCaseIdsByContactId($otherContactId, TRUE);
1975 }
1976 if (!is_array($processCaseIds)) {
1977 return;
1978 }
1979 }
1980
1981 $session = CRM_Core_Session::singleton();
1982 $currentUserId = $session->get('userID');
1983
02094cdb
JJ
1984 CRM_Utils_Hook::pre_case_merge($mainContactId, $mainCaseId, $otherContactId, $otherCaseId, $changeClient);
1985
6a488035
TO
1986 // copy all cases and connect to main contact id.
1987 foreach ($processCaseIds as $otherCaseId) {
1988 if ($duplicateContacts) {
fc9f05e0 1989 $mainCase = CRM_Core_DAO::copyGeneric('CRM_Case_DAO_Case', ['id' => $otherCaseId]);
6a488035
TO
1990 $mainCaseId = $mainCase->id;
1991 if (!$mainCaseId) {
1992 continue;
1993 }
8bd86283 1994
6a488035
TO
1995 $mainCaseIds[] = $mainCaseId;
1996 //insert record for case contact.
1997 $otherCaseContact = new CRM_Case_DAO_CaseContact();
1998 $otherCaseContact->case_id = $otherCaseId;
1999 $otherCaseContact->find();
2000 while ($otherCaseContact->fetch()) {
2001 $mainCaseContact = new CRM_Case_DAO_CaseContact();
2002 $mainCaseContact->case_id = $mainCaseId;
2003 $mainCaseContact->contact_id = $otherCaseContact->contact_id;
2004 if ($mainCaseContact->contact_id == $otherContactId) {
2005 $mainCaseContact->contact_id = $mainContactId;
2006 }
2007 //avoid duplicate object.
2008 if (!$mainCaseContact->find(TRUE)) {
2009 $mainCaseContact->save();
2010 }
6a488035 2011 }
6a488035
TO
2012 }
2013 elseif (!$otherContactId) {
2014 $otherContactId = $mainContactId;
2015 }
2016
2017 if (!$mainCaseId || !$otherCaseId ||
2018 !$mainContactId || !$otherContactId
2019 ) {
2020 continue;
2021 }
2022
2023 // get all activities for other case.
fc9f05e0 2024 $otherCaseActivities = [];
6a488035
TO
2025 CRM_Core_DAO::commonRetrieveAll('CRM_Case_DAO_CaseActivity', 'case_id', $otherCaseId, $otherCaseActivities);
2026
2027 //for duplicate cases do not process singleton activities.
fc9f05e0 2028 $otherActivityIds = $singletonActivityIds = [];
6a488035 2029 foreach ($otherCaseActivities as $caseActivityId => $otherIds) {
9c1bc317 2030 $otherActId = $otherIds['activity_id'] ?? NULL;
6a488035
TO
2031 if (!$otherActId || in_array($otherActId, $otherActivityIds)) {
2032 continue;
2033 }
2034 $otherActivityIds[] = $otherActId;
2035 }
2036 if ($duplicateCases) {
2037 if ($openCaseType = array_search('Open Case', $activityTypes)) {
2038 $sql = "
2039SELECT id
2040 FROM civicrm_activity
2041 WHERE activity_type_id = $openCaseType
2042 AND id IN ( " . implode(',', array_values($otherActivityIds)) . ');';
2043 $dao = CRM_Core_DAO::executeQuery($sql);
2044 while ($dao->fetch()) {
2045 $singletonActivityIds[] = $dao->id;
2046 }
6a488035
TO
2047 }
2048 }
2049
2050 // migrate all activities and connect to main contact.
fc9f05e0 2051 $copiedActivityIds = $activityMappingIds = [];
6a488035
TO
2052 sort($otherActivityIds);
2053 foreach ($otherActivityIds as $otherActivityId) {
2054
2055 //for duplicate cases -
2056 //do not migrate singleton activities.
2057 if (!$otherActivityId || in_array($otherActivityId, $singletonActivityIds)) {
2058 continue;
2059 }
2060
2061 //migrate activity record.
2062 $otherActivity = new CRM_Activity_DAO_Activity();
2063 $otherActivity->id = $otherActivityId;
2064 if (!$otherActivity->find(TRUE)) {
2065 continue;
2066 }
2067
fc9f05e0 2068 $mainActVals = [];
6a488035
TO
2069 $mainActivity = new CRM_Activity_DAO_Activity();
2070 CRM_Core_DAO::storeValues($otherActivity, $mainActVals);
2071 $mainActivity->copyValues($mainActVals);
2072 $mainActivity->id = NULL;
629c4d30 2073 $mainActivity->activity_date_time = $otherActivity->activity_date_time;
6a488035
TO
2074 $mainActivity->source_record_id = CRM_Utils_Array::value($mainActivity->source_record_id,
2075 $activityMappingIds
2076 );
2077
2078 $mainActivity->original_id = CRM_Utils_Array::value($mainActivity->original_id,
2079 $activityMappingIds
2080 );
2081
2082 $mainActivity->parent_id = CRM_Utils_Array::value($mainActivity->parent_id,
2083 $activityMappingIds
2084 );
2085 $mainActivity->save();
2086 $mainActivityId = $mainActivity->id;
2087 if (!$mainActivityId) {
2088 continue;
2089 }
2090
2091 $activityMappingIds[$otherActivityId] = $mainActivityId;
4322672b 2092 // insert log of all activities
6a488035
TO
2093 CRM_Activity_BAO_Activity::logActivityAction($mainActivity);
2094
6a488035
TO
2095 $copiedActivityIds[] = $otherActivityId;
2096
2097 //create case activity record.
2098 $mainCaseActivity = new CRM_Case_DAO_CaseActivity();
2099 $mainCaseActivity->case_id = $mainCaseId;
2100 $mainCaseActivity->activity_id = $mainActivityId;
2101 $mainCaseActivity->save();
6a488035 2102
4322672b 2103 //migrate source activity.
2104 $otherSourceActivity = new CRM_Activity_DAO_ActivityContact();
2105 $otherSourceActivity->activity_id = $otherActivityId;
2106 $otherSourceActivity->record_type_id = $sourceID;
2107 $otherSourceActivity->find();
2108 while ($otherSourceActivity->fetch()) {
2109 $mainActivitySource = new CRM_Activity_DAO_ActivityContact();
2110 $mainActivitySource->record_type_id = $sourceID;
2111 $mainActivitySource->activity_id = $mainActivityId;
2112 $mainActivitySource->contact_id = $otherSourceActivity->contact_id;
2113 if ($mainActivitySource->contact_id == $otherContactId) {
2114 $mainActivitySource->contact_id = $mainContactId;
2115 }
2116 //avoid duplicate object.
2117 if (!$mainActivitySource->find(TRUE)) {
2118 $mainActivitySource->save();
2119 }
4322672b 2120 }
4322672b 2121
6a488035 2122 //migrate target activities.
4e3d3cfc 2123 $otherTargetActivity = new CRM_Activity_DAO_ActivityContact();
6a488035 2124 $otherTargetActivity->activity_id = $otherActivityId;
9e74e3ce 2125 $otherTargetActivity->record_type_id = $targetID;
6a488035
TO
2126 $otherTargetActivity->find();
2127 while ($otherTargetActivity->fetch()) {
4e3d3cfc 2128 $mainActivityTarget = new CRM_Activity_DAO_ActivityContact();
9e74e3ce 2129 $mainActivityTarget->record_type_id = $targetID;
6a488035 2130 $mainActivityTarget->activity_id = $mainActivityId;
00bf7e59 2131 $mainActivityTarget->contact_id = $otherTargetActivity->contact_id;
2132 if ($mainActivityTarget->contact_id == $otherContactId) {
2133 $mainActivityTarget->contact_id = $mainContactId;
6a488035
TO
2134 }
2135 //avoid duplicate object.
2136 if (!$mainActivityTarget->find(TRUE)) {
2137 $mainActivityTarget->save();
2138 }
6a488035 2139 }
6a488035
TO
2140
2141 //migrate assignee activities.
4e3d3cfc 2142 $otherAssigneeActivity = new CRM_Activity_DAO_ActivityContact();
6a488035 2143 $otherAssigneeActivity->activity_id = $otherActivityId;
9e74e3ce 2144 $otherAssigneeActivity->record_type_id = $assigneeID;
6a488035
TO
2145 $otherAssigneeActivity->find();
2146 while ($otherAssigneeActivity->fetch()) {
4e3d3cfc 2147 $mainAssigneeActivity = new CRM_Activity_DAO_ActivityContact();
6a488035 2148 $mainAssigneeActivity->activity_id = $mainActivityId;
9e74e3ce 2149 $mainAssigneeActivity->record_type_id = $assigneeID;
00bf7e59 2150 $mainAssigneeActivity->contact_id = $otherAssigneeActivity->contact_id;
2151 if ($mainAssigneeActivity->contact_id == $otherContactId) {
2152 $mainAssigneeActivity->contact_id = $mainContactId;
6a488035
TO
2153 }
2154 //avoid duplicate object.
2155 if (!$mainAssigneeActivity->find(TRUE)) {
2156 $mainAssigneeActivity->save();
2157 }
6a488035 2158 }
8c31eef5
D
2159
2160 // copy custom fields and attachments
fc9f05e0 2161 $aparams = [
4322672b 2162 'activityID' => $otherActivityId,
2163 'mainActivityId' => $mainActivityId,
fc9f05e0 2164 ];
8c31eef5 2165 CRM_Activity_BAO_Activity::copyExtendedActivityData($aparams);
6a488035
TO
2166 }
2167
2168 //copy case relationship.
2169 if ($duplicateContacts) {
2170 //migrate relationship records.
2171 $otherRelationship = new CRM_Contact_DAO_Relationship();
2172 $otherRelationship->case_id = $otherCaseId;
2173 $otherRelationship->find();
fc9f05e0 2174 $otherRelationshipIds = [];
6a488035 2175 while ($otherRelationship->fetch()) {
fc9f05e0 2176 $otherRelVals = [];
6a488035
TO
2177 $updateOtherRel = FALSE;
2178 CRM_Core_DAO::storeValues($otherRelationship, $otherRelVals);
2179
2180 $mainRelationship = new CRM_Contact_DAO_Relationship();
2181 $mainRelationship->copyValues($otherRelVals);
2182 $mainRelationship->id = NULL;
2183 $mainRelationship->case_id = $mainCaseId;
2184 if ($mainRelationship->contact_id_a == $otherContactId) {
2185 $updateOtherRel = TRUE;
2186 $mainRelationship->contact_id_a = $mainContactId;
2187 }
2188
2189 //case creator change only when we merge user contact.
2190 if ($mainRelationship->contact_id_b == $otherContactId) {
2191 //do not change creator for change client.
2192 if (!$changeClient) {
2193 $updateOtherRel = TRUE;
2194 $mainRelationship->contact_id_b = ($currentUserId) ? $currentUserId : $mainContactId;
2195 }
2196 }
2197 $mainRelationship->end_date = CRM_Utils_Date::isoToMysql($otherRelationship->end_date);
2198 $mainRelationship->start_date = CRM_Utils_Date::isoToMysql($otherRelationship->start_date);
2199
2200 //avoid duplicate object.
2201 if (!$mainRelationship->find(TRUE)) {
2202 $mainRelationship->save();
2203 }
6a488035
TO
2204
2205 //get the other relationship ids to update end date.
2206 if ($updateOtherRel) {
2207 $otherRelationshipIds[$otherRelationship->id] = $otherRelationship->id;
2208 }
2209 }
6a488035
TO
2210
2211 //update other relationships end dates
2212 if (!empty($otherRelationshipIds)) {
2213 $sql = 'UPDATE civicrm_relationship
2214 SET end_date = CURDATE()
2215 WHERE id IN ( ' . implode(',', $otherRelationshipIds) . ')';
2216 CRM_Core_DAO::executeQuery($sql);
2217 }
2218 }
2219
2220 //move other case to trash.
2221 $mergeCase = self::deleteCase($otherCaseId, $moveToTrash);
2222 if (!$mergeCase) {
2223 continue;
2224 }
2225
2226 $mergeActSubject = $mergeActSubjectDetails = $mergeActType = '';
2227 if ($changeClient) {
2228 $mainContactDisplayName = CRM_Contact_BAO_Contact::displayName($mainContactId);
2229 $otherContactDisplayName = CRM_Contact_BAO_Contact::displayName($otherContactId);
2230
2231 $mergeActType = array_search('Reassigned Case', $activityTypes);
2232 $mergeActSubject = ts("Case %1 reassigned client from %2 to %3. New Case ID is %4.",
fc9f05e0 2233 [
e96f025a 2234 1 => $otherCaseId,
2235 2 => $otherContactDisplayName,
2236 3 => $mainContactDisplayName,
21dfd5f5 2237 4 => $mainCaseId,
fc9f05e0 2238 ]
6a488035
TO
2239 );
2240 }
2241 elseif ($duplicateContacts) {
2242 $mergeActType = array_search('Merge Case', $activityTypes);
2243 $mergeActSubject = ts("Case %1 copied from contact id %2 to contact id %3 via merge. New Case ID is %4.",
fc9f05e0 2244 [
e96f025a 2245 1 => $otherCaseId,
2246 2 => $otherContactId,
2247 3 => $mainContactId,
21dfd5f5 2248 4 => $mainCaseId,
fc9f05e0 2249 ]
6a488035
TO
2250 );
2251 }
2252 else {
2253 $mergeActType = array_search('Merge Case', $activityTypes);
fc9f05e0 2254 $mergeActSubject = ts("Case %1 merged into case %2", [1 => $otherCaseId, 2 => $mainCaseId]);
6a488035
TO
2255 if (!empty($copiedActivityIds)) {
2256 $sql = '
2257SELECT id, subject, activity_date_time, activity_type_id
2258FROM civicrm_activity
2259WHERE id IN (' . implode(',', $copiedActivityIds) . ')';
2260 $dao = CRM_Core_DAO::executeQuery($sql);
2261 while ($dao->fetch()) {
2262 $mergeActSubjectDetails .= "{$dao->activity_date_time} :: {$activityTypes[$dao->activity_type_id]}";
2263 if ($dao->subject) {
2264 $mergeActSubjectDetails .= " :: {$dao->subject}";
2265 }
2266 $mergeActSubjectDetails .= "<br />";
2267 }
2268 }
2269 }
2270
b864360d 2271 // Create merge activity record. Source for merge activity is the logged in user's contact ID ($currentUserId).
fc9f05e0 2272 $activityParams = [
6a488035
TO
2273 'subject' => $mergeActSubject,
2274 'details' => $mergeActSubjectDetails,
b864360d 2275 'status_id' => $completedActivityStatus,
6a488035 2276 'activity_type_id' => $mergeActType,
8c677b07 2277 'source_contact_id' => $currentUserId,
6a488035 2278 'activity_date_time' => date('YmdHis'),
fc9f05e0 2279 ];
6a488035
TO
2280
2281 $mergeActivity = CRM_Activity_BAO_Activity::create($activityParams);
2282 $mergeActivityId = $mergeActivity->id;
2283 if (!$mergeActivityId) {
2284 continue;
2285 }
6a488035
TO
2286
2287 //connect merge activity to case.
fc9f05e0 2288 $mergeCaseAct = [
6a488035
TO
2289 'case_id' => $mainCaseId,
2290 'activity_id' => $mergeActivityId,
fc9f05e0 2291 ];
6a488035
TO
2292
2293 self::processCaseActivity($mergeCaseAct);
2294 }
02094cdb
JJ
2295
2296 CRM_Utils_Hook::post_case_merge($mainContactId, $mainCaseId, $otherContactId, $otherCaseId, $changeClient);
2297
6a488035
TO
2298 return $mainCaseIds;
2299 }
2300
2301 /**
2302 * Validate contact permission for
2303 * edit/view on activity record and build links.
2304 *
64bd5a0e
TO
2305 * @param array $tplParams
2306 * Params to be sent to template for sending email.
2307 * @param array $activityParams
2308 * Info of the activity.
6a488035 2309 */
00be9182 2310 public static function buildPermissionLinks(&$tplParams, $activityParams) {
6a488035
TO
2311 $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityParams['source_record_id'],
2312 'activity_type_id', 'id'
2313 );
2314
a7488080 2315 if (!empty($tplParams['isCaseActivity'])) {
6a488035
TO
2316 $tplParams['editActURL'] = CRM_Utils_System::url('civicrm/case/activity',
2317 "reset=1&cid={$activityParams['target_id']}&caseid={$activityParams['case_id']}&action=update&id={$activityParams['source_record_id']}", TRUE
2318 );
2319
2320 $tplParams['viewActURL'] = CRM_Utils_System::url('civicrm/case/activity/view',
2321 "reset=1&aid={$activityParams['source_record_id']}&cid={$activityParams['target_id']}&caseID={$activityParams['case_id']}", TRUE
2322 );
2323
2324 $tplParams['manageCaseURL'] = CRM_Utils_System::url('civicrm/contact/view/case',
2325 "reset=1&id={$activityParams['case_id']}&cid={$activityParams['target_id']}&action=view&context=home", TRUE
2326 );
2327 }
2328 else {
2329 $tplParams['editActURL'] = CRM_Utils_System::url('civicrm/contact/view/activity',
2330 "atype=$activityTypeId&action=update&reset=1&id={$activityParams['source_record_id']}&cid={$tplParams['contact']['contact_id']}&context=activity", TRUE
2331 );
2332
2333 $tplParams['viewActURL'] = CRM_Utils_System::url('civicrm/contact/view/activity',
2334 "atype=$activityTypeId&action=view&reset=1&id={$activityParams['source_record_id']}&cid={$tplParams['contact']['contact_id']}&context=activity", TRUE
2335 );
2336 }
2337 }
2338
2339 /**
2340 * Validate contact permission for
2341 * given operation on activity record.
2342 *
64bd5a0e
TO
2343 * @param int $activityId
2344 * Activity record id.
2345 * @param string $operation
2346 * User operation.
2347 * @param int $actTypeId
2348 * Activity type id.
2349 * @param int $contactId
2350 * Contact id/if not pass consider logged in.
2351 * @param bool $checkComponent
2352 * Do we need to check component enabled.
6a488035 2353 *
a130e045 2354 * @return bool
6a488035 2355 */
00be9182 2356 public static function checkPermission($activityId, $operation, $actTypeId = NULL, $contactId = NULL, $checkComponent = TRUE) {
6a488035
TO
2357 $allow = FALSE;
2358 if (!$actTypeId && $activityId) {
2359 $actTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, 'activity_type_id');
2360 }
2361
2362 if (!$activityId || !$operation || !$actTypeId) {
2363 return $allow;
2364 }
2365
2366 //do check for civicase component enabled.
077dbf5e
CW
2367 if ($checkComponent && !self::enabled()) {
2368 return $allow;
6a488035
TO
2369 }
2370
2371 //do check for cases.
fc9f05e0 2372 $caseActOperations = [
6a488035
TO
2373 'File On Case',
2374 'Link Cases',
2375 'Move To Case',
2376 'Copy To Case',
fc9f05e0 2377 ];
6a488035
TO
2378
2379 if (in_array($operation, $caseActOperations)) {
abd06efc
CW
2380 static $caseCount;
2381 if (!isset($caseCount)) {
eeb45e43 2382 try {
fc9f05e0 2383 $caseCount = civicrm_api3('Case', 'getcount', [
eeb45e43 2384 'check_permissions' => TRUE,
fc9f05e0 2385 'status_id' => ['!=' => 'Closed'],
eeb45e43 2386 'is_deleted' => 0,
fc9f05e0 2387 'end_date' => ['IS NULL' => 1],
2388 ]);
eeb45e43
CW
2389 }
2390 catch (CiviCRM_API3_Exception $e) {
2391 // Lack of permissions will throw an exception
2392 $caseCount = 0;
2393 }
6a488035
TO
2394 }
2395 if ($operation == 'File On Case') {
abd06efc 2396 $allow = !empty($caseCount);
6a488035
TO
2397 }
2398 else {
abd06efc 2399 $allow = ($caseCount > 1);
6a488035
TO
2400 }
2401 }
2402
fc9f05e0 2403 $actionOperations = ['view', 'edit', 'delete'];
6a488035
TO
2404 if (in_array($operation, $actionOperations)) {
2405
2406 //do cache when user has non/supper permission.
2407 static $allowOperations;
2408
2409 if (!is_array($allowOperations) ||
2410 !array_key_exists($operation, $allowOperations)
2411 ) {
2412
2413 if (!$contactId) {
2414 $session = CRM_Core_Session::singleton();
2415 $contactId = $session->get('userID');
2416 }
2417
2418 //check for permissions.
fc9f05e0 2419 $permissions = [
2420 'view' => [
6a488035
TO
2421 'access my cases and activities',
2422 'access all cases and activities',
fc9f05e0 2423 ],
2424 'edit' => [
6a488035
TO
2425 'access my cases and activities',
2426 'access all cases and activities',
fc9f05e0 2427 ],
2428 'delete' => ['delete activities'],
2429 ];
6a488035
TO
2430
2431 //check for core permission.
fc9f05e0 2432 $hasPermissions = [];
9c1bc317 2433 $checkPermissions = $permissions[$operation] ?? NULL;
6a488035
TO
2434 if (is_array($checkPermissions)) {
2435 foreach ($checkPermissions as $per) {
2436 if (CRM_Core_Permission::check($per)) {
2437 $hasPermissions[$operation][] = $per;
2438 }
2439 }
2440 }
2441
2442 //has permissions.
2443 if (!empty($hasPermissions)) {
2444 //need to check activity object specific.
fc9f05e0 2445 if (in_array($operation, [
e96f025a 2446 'view',
21dfd5f5 2447 'edit',
fc9f05e0 2448 ])
e96f025a 2449 ) {
6a488035
TO
2450 //do we have supper permission.
2451 if (in_array('access all cases and activities', $hasPermissions[$operation])) {
2452 $allowOperations[$operation] = $allow = TRUE;
2453 }
2454 else {
2455 //user has only access to my cases and activity.
2456 //here object specific permmions come in picture.
2457
2458 //edit - contact must be source or assignee
2459 //view - contact must be source/assignee/target
2460 $isTarget = $isAssignee = $isSource = FALSE;
44f817d4 2461 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
4322672b 2462 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
2463 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
2464 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
6a488035 2465
4e3d3cfc 2466 $target = new CRM_Activity_DAO_ActivityContact();
9e74e3ce 2467 $target->record_type_id = $targetID;
6a488035 2468 $target->activity_id = $activityId;
00bf7e59 2469 $target->contact_id = $contactId;
6a488035
TO
2470 if ($target->find(TRUE)) {
2471 $isTarget = TRUE;
2472 }
2473
4e3d3cfc 2474 $assignee = new CRM_Activity_DAO_ActivityContact();
6a488035 2475 $assignee->activity_id = $activityId;
9e74e3ce 2476 $assignee->record_type_id = $assigneeID;
00bf7e59 2477 $assignee->contact_id = $contactId;
6a488035
TO
2478 if ($assignee->find(TRUE)) {
2479 $isAssignee = TRUE;
2480 }
2481
4322672b 2482 $source = new CRM_Activity_DAO_ActivityContact();
2483 $source->activity_id = $activityId;
2484 $source->record_type_id = $sourceID;
2485 $source->contact_id = $contactId;
2486 if ($source->find(TRUE)) {
6a488035
TO
2487 $isSource = TRUE;
2488 }
2489
2490 if ($operation == 'edit') {
2491 if ($isAssignee || $isSource) {
2492 $allow = TRUE;
2493 }
2494 }
2495 if ($operation == 'view') {
2496 if ($isTarget || $isAssignee || $isSource) {
2497 $allow = TRUE;
2498 }
2499 }
2500 }
2501 }
2502 elseif (is_array($hasPermissions[$operation])) {
2503 $allowOperations[$operation] = $allow = TRUE;
2504 }
2505 }
2506 else {
2507 //contact do not have permission.
2508 $allowOperations[$operation] = FALSE;
2509 }
2510 }
2511 else {
2512 //use cache.
2513 //here contact might have supper/non permission.
2514 $allow = $allowOperations[$operation];
2515 }
2516 }
2517
2518 //do further only when operation is granted.
2519 if ($allow) {
b864360d 2520 $actTypeName = CRM_Core_PseudoConstant::getName('CRM_Activity_BAO_Activity', 'activity_type_id', $actTypeId);
6a488035
TO
2521
2522 //do not allow multiple copy / edit action.
fc9f05e0 2523 $singletonNames = [
e96f025a 2524 'Open Case',
2525 'Reassigned Case',
2526 'Merge Case',
2527 'Link Cases',
2528 'Assign Case Role',
2529 'Email',
21dfd5f5 2530 'Inbound Email',
fc9f05e0 2531 ];
6a488035
TO
2532
2533 //do not allow to delete these activities, CRM-4543
fc9f05e0 2534 $doNotDeleteNames = ['Open Case', 'Change Case Type', 'Change Case Status', 'Change Case Start Date'];
6a488035
TO
2535
2536 //allow edit operation.
fc9f05e0 2537 $allowEditNames = ['Open Case'];
6a488035 2538
426fe3c7 2539 if (CRM_Activity_BAO_Activity::checkEditInboundEmailsPermissions()) {
ee90a98c
CR
2540 $allowEditNames[] = 'Inbound Email';
2541 }
2542
6a488035 2543 // do not allow File on Case
fc9f05e0 2544 $doNotFileNames = [
e96f025a 2545 'Open Case',
2546 'Change Case Type',
2547 'Change Case Status',
2548 'Change Case Start Date',
2549 'Reassigned Case',
2550 'Merge Case',
2551 'Link Cases',
21dfd5f5 2552 'Assign Case Role',
fc9f05e0 2553 ];
6a488035
TO
2554
2555 if (in_array($actTypeName, $singletonNames)) {
2556 $allow = FALSE;
2557 if ($operation == 'File On Case') {
f7dbf5d9 2558 $allow = !in_array($actTypeName, $doNotFileNames);
6a488035
TO
2559 }
2560 if (in_array($operation, $actionOperations)) {
2561 $allow = TRUE;
2562 if ($operation == 'edit') {
f7dbf5d9 2563 $allow = in_array($actTypeName, $allowEditNames);
6a488035
TO
2564 }
2565 elseif ($operation == 'delete') {
f7dbf5d9 2566 $allow = !in_array($actTypeName, $doNotDeleteNames);
6a488035
TO
2567 }
2568 }
2569 }
2570 if ($allow && ($operation == 'delete') &&
2571 in_array($actTypeName, $doNotDeleteNames)
2572 ) {
2573 $allow = FALSE;
2574 }
2575
2576 if ($allow && ($operation == 'File On Case') &&
2577 in_array($actTypeName, $doNotFileNames)
2578 ) {
2579 $allow = FALSE;
2580 }
2581
2582 //check settings file for masking actions
2583 //on the basis the activity types
2584 //hide Edit link if activity type is NOT editable
2585 //(special case activities).CRM-5871
2586 if ($allow && in_array($operation, $actionOperations)) {
fc9f05e0 2587 static $actionFilter = [];
6a488035
TO
2588 if (!array_key_exists($operation, $actionFilter)) {
2589 $xmlProcessor = new CRM_Case_XMLProcessor_Process();
2590 $actionFilter[$operation] = $xmlProcessor->get('Settings', 'ActivityTypes', FALSE, $operation);
2591 }
2592 if (array_key_exists($operation, $actionFilter[$operation]) &&
2593 in_array($actTypeId, $actionFilter[$operation][$operation])
2594 ) {
2595 $allow = FALSE;
2596 }
2597 }
2598 }
2599
2600 return $allow;
2601 }
2602
2603 /**
100fef9d 2604 * Since we drop 'access CiviCase', allow access
6a488035
TO
2605 * if user has 'access my cases and activities'
2606 * or 'access all cases and activities'
2607 */
00be9182 2608 public static function accessCiviCase() {
077dbf5e 2609 if (!self::enabled()) {
6a488035
TO
2610 return FALSE;
2611 }
2612
2613 if (CRM_Core_Permission::check('access my cases and activities') ||
2614 CRM_Core_Permission::check('access all cases and activities')
2615 ) {
2616 return TRUE;
2617 }
2618
2619 return FALSE;
2620 }
2621
2622 /**
d2e5d2ce 2623 * Verify user has permission to access a case.
077dbf5e
CW
2624 *
2625 * @param int $caseId
64bd5a0e
TO
2626 * @param bool $denyClosed
2627 * Set TRUE if one wants closed cases to be treated as inaccessible.
077dbf5e
CW
2628 *
2629 * @return bool
2630 */
00be9182 2631 public static function accessCase($caseId, $denyClosed = TRUE) {
077dbf5e
CW
2632 if (!$caseId || !self::enabled()) {
2633 return FALSE;
2634 }
2635
fc9f05e0 2636 $params = ['id' => $caseId, 'check_permissions' => TRUE];
3924e596 2637 if ($denyClosed && !CRM_Core_Permission::check('access all cases and activities')) {
fc9f05e0 2638 $params['status_id'] = ['!=' => 'Closed'];
546abeb1 2639 }
eeb45e43
CW
2640 try {
2641 return (bool) civicrm_api3('Case', 'getcount', $params);
2642 }
2643 catch (CiviCRM_API3_Exception $e) {
2644 // Lack of permissions will throw an exception
2645 return FALSE;
2646 }
077dbf5e
CW
2647 }
2648
6a488035 2649 /**
d2e5d2ce 2650 * Check whether activity is a case Activity.
6a488035 2651 *
64bd5a0e
TO
2652 * @param int $activityID
2653 * Activity id.
6a488035 2654 *
a130e045 2655 * @return bool
6a488035 2656 */
00be9182 2657 public static function isCaseActivity($activityID) {
6a488035
TO
2658 $isCaseActivity = FALSE;
2659 if ($activityID) {
fc9f05e0 2660 $params = [1 => [$activityID, 'Integer']];
6a488035
TO
2661 $query = "SELECT id FROM civicrm_case_activity WHERE activity_id = %1";
2662 if (CRM_Core_DAO::singleValueQuery($query, $params)) {
2663 $isCaseActivity = TRUE;
2664 }
2665 }
2666
2667 return $isCaseActivity;
2668 }
2669
2670 /**
d2e5d2ce 2671 * Get all the case type ids currently in use.
6a488035 2672 *
a6c01b45 2673 * @return array
6a488035 2674 */
00be9182 2675 public static function getUsedCaseType() {
6a488035
TO
2676 static $caseTypeIds;
2677
2678 if (!is_array($caseTypeIds)) {
2679 $query = "SELECT DISTINCT( civicrm_case.case_type_id ) FROM civicrm_case";
2680
2681 $dao = CRM_Core_DAO::executeQuery($query);
fc9f05e0 2682 $caseTypeIds = [];
6a488035
TO
2683 while ($dao->fetch()) {
2684 $typeId = explode(CRM_Core_DAO::VALUE_SEPARATOR,
2685 $dao->case_type_id
2686 );
2687 $caseTypeIds[] = $typeId[1];
2688 }
2689 }
2690
2691 return $caseTypeIds;
2692 }
2693
2694 /**
d2e5d2ce 2695 * Get all the case status ids currently in use.
6a488035 2696 *
a6c01b45 2697 * @return array
6a488035 2698 */
00be9182 2699 public static function getUsedCaseStatuses() {
6a488035
TO
2700 static $caseStatusIds;
2701
2702 if (!is_array($caseStatusIds)) {
2703 $query = "SELECT DISTINCT( civicrm_case.status_id ) FROM civicrm_case";
2704
2705 $dao = CRM_Core_DAO::executeQuery($query);
fc9f05e0 2706 $caseStatusIds = [];
6a488035
TO
2707 while ($dao->fetch()) {
2708 $caseStatusIds[] = $dao->status_id;
2709 }
2710 }
2711
2712 return $caseStatusIds;
2713 }
2714
2715 /**
d2e5d2ce 2716 * Get all the encounter medium ids currently in use.
72b3a70c 2717 *
6a488035
TO
2718 * @return array
2719 */
00be9182 2720 public static function getUsedEncounterMediums() {
6a488035
TO
2721 static $mediumIds;
2722
2723 if (!is_array($mediumIds)) {
2724 $query = "SELECT DISTINCT( civicrm_activity.medium_id ) FROM civicrm_activity";
2725
2726 $dao = CRM_Core_DAO::executeQuery($query);
fc9f05e0 2727 $mediumIds = [];
6a488035
TO
2728 while ($dao->fetch()) {
2729 $mediumIds[] = $dao->medium_id;
2730 }
2731 }
2732
2733 return $mediumIds;
2734 }
2735
2736 /**
100fef9d 2737 * Check case configuration.
6a488035 2738 *
100fef9d 2739 * @param int $contactId
77b97be7 2740 *
a6c01b45 2741 * @return array
6a488035 2742 */
00be9182 2743 public static function isCaseConfigured($contactId = NULL) {
fc9f05e0 2744 $configured = array_fill_keys(['configured', 'allowToAddNewCase', 'redirectToCaseAdmin'], FALSE);
6a488035
TO
2745
2746 //lets check for case configured.
2747 $allCasesCount = CRM_Case_BAO_Case::caseCount(NULL, FALSE);
f7dbf5d9 2748 $configured['configured'] = (bool) $allCasesCount;
6a488035
TO
2749 if (!$configured['configured']) {
2750 //do check for case type and case status.
0372ffa2 2751 $caseTypes = CRM_Case_PseudoConstant::caseType('title', FALSE);
6a488035
TO
2752 if (!empty($caseTypes)) {
2753 $configured['configured'] = TRUE;
2754 if (!$configured['configured']) {
2755 $caseStatuses = CRM_Case_PseudoConstant::caseStatus('label', FALSE);
2756 if (!empty($caseStatuses)) {
2757 $configured['configured'] = TRUE;
2758 }
2759 }
2760 }
2761 }
2762 if ($configured['configured']) {
2763 //do check for active case type and case status.
2764 $caseTypes = CRM_Case_PseudoConstant::caseType();
2765 if (!empty($caseTypes)) {
2766 $caseStatuses = CRM_Case_PseudoConstant::caseStatus();
2767 if (!empty($caseStatuses)) {
2768 $configured['allowToAddNewCase'] = TRUE;
2769 }
2770 }
2771
2772 //do we need to redirect user to case admin.
2773 if (!$configured['allowToAddNewCase'] && $contactId) {
2774 //check for current contact case count.
2775 $currentContatCasesCount = CRM_Case_BAO_Case::caseCount($contactId);
2776 //redirect user to case admin page.
2777 if (!$currentContatCasesCount) {
2778 $configured['redirectToCaseAdmin'] = TRUE;
2779 }
2780 }
2781 }
2782
2783 return $configured;
2784 }
2785
d6f468d3 2786 /**
d2e5d2ce 2787 * Used during case component enablement and during ugprade.
72b3a70c
CW
2788 *
2789 * @return bool
6a488035 2790 */
00be9182 2791 public static function createCaseViews() {
6a4257d4 2792 $errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
a19fc402
TO
2793 $dao = new CRM_Core_DAO();
2794
6a488035 2795 $sql = self::createCaseViewsQuery('upcoming');
6a488035
TO
2796 $dao->query($sql);
2797 if (PEAR::getStaticProperty('DB_DataObject', 'lastError')) {
6a488035
TO
2798 return FALSE;
2799 }
2800
2801 // Above error doesn't get caught?
2802 $doublecheck = $dao->singleValueQuery("SELECT count(id) FROM civicrm_view_case_activity_upcoming");
2803 if (is_null($doublecheck)) {
2804 return FALSE;
2805 }
2806
2807 $sql = self::createCaseViewsQuery('recent');
6a488035
TO
2808 $dao->query($sql);
2809 if (PEAR::getStaticProperty('DB_DataObject', 'lastError')) {
6a488035
TO
2810 return FALSE;
2811 }
2812
2813 // Above error doesn't get caught?
2814 $doublecheck = $dao->singleValueQuery("SELECT count(id) FROM civicrm_view_case_activity_recent");
2815 if (is_null($doublecheck)) {
2816 return FALSE;
2817 }
2818
2819 return TRUE;
2820 }
2821
d6f468d3 2822 /**
100fef9d 2823 * Helper function, also used by the upgrade in case of error
72b3a70c 2824 *
cde2037d
EM
2825 * @param string $section
2826 *
72b3a70c 2827 * @return string
6a488035 2828 */
00be9182 2829 public static function createCaseViewsQuery($section = 'upcoming') {
6a488035 2830 $sql = "";
0db2818f 2831 $scheduled_id = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Scheduled');
6a488035
TO
2832 switch ($section) {
2833 case 'upcoming':
2834 $sql = "CREATE OR REPLACE VIEW `civicrm_view_case_activity_upcoming`
2835 AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id
2836 FROM civicrm_case_activity ca
2837 INNER JOIN civicrm_activity a ON ca.activity_id=a.id
66d84429
AH
2838 WHERE a.id =
2839(SELECT b.id FROM civicrm_case_activity bca
2b95a25b 2840 INNER JOIN civicrm_activity b ON bca.activity_id=b.id
2841 WHERE b.activity_date_time <= DATE_ADD( NOW(), INTERVAL 14 DAY )
2842 AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id = $scheduled_id
2843 AND bca.case_id = ca.case_id ORDER BY b.activity_date_time ASC LIMIT 1)";
6a488035
TO
2844 break;
2845
2846 case 'recent':
2847 $sql = "CREATE OR REPLACE VIEW `civicrm_view_case_activity_recent`
2848 AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id
2849 FROM civicrm_case_activity ca
2850 INNER JOIN civicrm_activity a ON ca.activity_id=a.id
66d84429
AH
2851 WHERE a.id =
2852(SELECT b.id FROM civicrm_case_activity bca
2b95a25b 2853 INNER JOIN civicrm_activity b ON bca.activity_id=b.id
2854 WHERE b.activity_date_time >= DATE_SUB( NOW(), INTERVAL 14 DAY )
2855 AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id <> $scheduled_id
2856 AND bca.case_id = ca.case_id ORDER BY b.activity_date_time DESC LIMIT 1)";
6a488035
TO
2857 break;
2858 }
6a488035 2859 return $sql;
e96f025a 2860 }
2861
2862 /**
100fef9d 2863 * Add/copy relationships, when new client is added for a case
e96f025a 2864 *
64bd5a0e
TO
2865 * @param int $caseId
2866 * Case id.
2867 * @param int $contactId
2868 * Contact id / new client id.
e96f025a 2869 */
00be9182 2870 public static function addCaseRelationships($caseId, $contactId) {
44466d80
KJ
2871 // get the case role / relationships for the case
2872 $caseRelationships = new CRM_Contact_DAO_Relationship();
2873 $caseRelationships->case_id = $caseId;
2874 $caseRelationships->find();
fc9f05e0 2875 $relationshipTypes = [];
44466d80
KJ
2876
2877 // make sure we don't add duplicate relationships of same relationship type.
2878 while ($caseRelationships->fetch() && !in_array($caseRelationships->relationship_type_id, $relationshipTypes)) {
fc9f05e0 2879 $values = [];
44466d80
KJ
2880 CRM_Core_DAO::storeValues($caseRelationships, $values);
2881
2882 // add relationship for new client.
2883 $newRelationship = new CRM_Contact_DAO_Relationship();
2884 $newRelationship->copyValues($values);
2885 $newRelationship->id = NULL;
2886 $newRelationship->case_id = $caseId;
2887 $newRelationship->contact_id_a = $contactId;
2888 $newRelationship->end_date = CRM_Utils_Date::isoToMysql($caseRelationships->end_date);
2889 $newRelationship->start_date = CRM_Utils_Date::isoToMysql($caseRelationships->start_date);
2890
2891 // another check to avoid duplicate relationship, in cases where client is removed and re-added again.
2892 if (!$newRelationship->find(TRUE)) {
2893 $newRelationship->save();
2894 }
44466d80
KJ
2895
2896 // store relationship type of newly created relationship
2897 $relationshipTypes[] = $caseRelationships->relationship_type_id;
2898 }
6a488035 2899 }
14a679f1
KJ
2900
2901 /**
d2e5d2ce 2902 * Get the list of clients for a case.
14a679f1
KJ
2903 *
2904 * @param int $caseId
2905 *
a6c01b45
CW
2906 * @return array
2907 * associated array with client ids
14a679f1 2908 */
00be9182 2909 public static function getCaseClients($caseId) {
fc9f05e0 2910 $clients = [];
14a679f1
KJ
2911 $caseContact = new CRM_Case_DAO_CaseContact();
2912 $caseContact->case_id = $caseId;
78762324 2913 $caseContact->orderBy('id');
14a679f1
KJ
2914 $caseContact->find();
2915
e96f025a 2916 while ($caseContact->fetch()) {
14a679f1
KJ
2917 $clients[] = $caseContact->contact_id;
2918 }
2919
2920 return $clients;
2921 }
16c0ec8d 2922
3b1c37fe
CW
2923 /**
2924 * @param int $caseId
2925 * @param string $direction
2926 * @param int $cid
2927 * @param int $relTypeId
fc9f05e0 2928 *
3b1c37fe
CW
2929 * @throws \CRM_Core_Exception
2930 * @throws \CiviCRM_API3_Exception
2931 */
2932 public static function endCaseRole($caseId, $direction, $cid, $relTypeId) {
2933 // Validate inputs
2934 if ($direction !== 'a' && $direction !== 'b') {
2935 throw new CRM_Core_Exception('Invalid relationship direction');
2936 }
2937
2938 // This case might have multiple clients, so we lookup by relationship instead of by id to get them all
2939 $sql = "SELECT id FROM civicrm_relationship WHERE case_id = %1 AND contact_id_{$direction} = %2 AND relationship_type_id = %3";
fc9f05e0 2940 $dao = CRM_Core_DAO::executeQuery($sql, [
2941 1 => [$caseId, 'Positive'],
2942 2 => [$cid, 'Positive'],
2943 3 => [$relTypeId, 'Positive'],
2944 ]);
3b1c37fe 2945 while ($dao->fetch()) {
fc9f05e0 2946 civicrm_api3('relationship', 'create', [
3b1c37fe
CW
2947 'id' => $dao->id,
2948 'is_active' => 0,
2949 'end_date' => 'now',
fc9f05e0 2950 ]);
3b1c37fe
CW
2951 }
2952 }
2953
16c0ec8d
CW
2954 /**
2955 * Get options for a given case field.
16c0ec8d 2956 *
64bd5a0e
TO
2957 * @param string $fieldName
2958 * @param string $context
64bd5a0e 2959 * @param array $props
72b3a70c 2960 * Whatever is known about this dao object.
77b97be7 2961 *
a130e045 2962 * @return array|bool
fc9f05e0 2963 * @throws \CiviCRM_API3_Exception
2964 *
2965 * @see CRM_Core_DAO::buildOptionsContext
2966 * @see CRM_Core_DAO::buildOptions
2967 *
16c0ec8d 2968 */
fc9f05e0 2969 public static function buildOptions($fieldName, $context = NULL, $props = []) {
16c0ec8d 2970 $className = __CLASS__;
fc9f05e0 2971 $params = [];
16c0ec8d
CW
2972 switch ($fieldName) {
2973 // This field is not part of this object but the api supports it
2974 case 'medium_id':
2975 $className = 'CRM_Activity_BAO_Activity';
2976 break;
31c28ed5
CW
2977
2978 // Filter status id by case type id
2979 case 'status_id':
2980 if (!empty($props['case_type_id'])) {
2981 $idField = is_numeric($props['case_type_id']) ? 'id' : 'name';
fc9f05e0 2982 $caseType = civicrm_api3('CaseType', 'getsingle', [$idField => $props['case_type_id'], 'return' => 'definition']);
31c28ed5
CW
2983 if (!empty($caseType['definition']['statuses'])) {
2984 $params['condition'] = 'v.name IN ("' . implode('","', $caseType['definition']['statuses']) . '")';
2985 }
2986 }
2987 break;
16c0ec8d
CW
2988 }
2989 return CRM_Core_PseudoConstant::get($className, $fieldName, $params, $context);
2990 }
96025800 2991
174a1918
CW
2992 /**
2993 * @inheritDoc
2994 */
20e41014 2995 public function addSelectWhereClause() {
0b80f0b4
CW
2996 // We always return an array with these keys, even if they are empty,
2997 // because this tells the query builder that we have considered these fields for acls
fc9f05e0 2998 $clauses = [
2999 'id' => [],
ff9340a4 3000 // Only case admins can view deleted cases
fc9f05e0 3001 'is_deleted' => CRM_Core_Permission::check('administer CiviCase') ? [] : ["= 0"],
3002 ];
174a1918 3003 // Ensure the user has permission to view the case client
d1d3c04a 3004 $contactClause = CRM_Utils_SQL::mergeSubquery('Contact');
ff9340a4
CW
3005 if ($contactClause) {
3006 $contactClause = implode(' AND contact_id ', $contactClause);
3007 $clauses['id'][] = "IN (SELECT case_id FROM civicrm_case_contact WHERE contact_id $contactClause)";
174a1918 3008 }
0b80f0b4 3009 // The api gatekeeper ensures the user has at least "access my cases and activities"
174a1918
CW
3010 // so if they do not have permission to see all cases we'll assume they can only access their own
3011 if (!CRM_Core_Permission::check('access all cases and activities')) {
3012 $user = (int) CRM_Core_Session::getLoggedInContactID();
ff9340a4 3013 $clauses['id'][] = "IN (
174a1918 3014 SELECT r.case_id FROM civicrm_relationship r, civicrm_case_contact cc WHERE r.is_active = 1 AND cc.case_id = r.case_id AND (
ff9340a4 3015 (r.contact_id_a = cc.contact_id AND r.contact_id_b = $user) OR (r.contact_id_b = cc.contact_id AND r.contact_id_a = $user)
174a1918
CW
3016 )
3017 )";
3018 }
2b240c0c 3019 CRM_Utils_Hook::selectWhereClause($this, $clauses);
ff9340a4 3020 return $clauses;
174a1918
CW
3021 }
3022
8aeeea00 3023 /**
3cf1fae9 3024 * CRM-20308: Method to get the contact id to use as from contact for email copy
8aeeea00
EH
3025 * 1. Activity Added by Contact's email address
3026 * 2. System Default From Address
3027 * 3. Default Organization Contact email address
3028 * 4. Logged in user
3029 *
3cf1fae9 3030 * @param int $activityID
3031 *
8aeeea00 3032 * @return mixed $emailFromContactId
fc9f05e0 3033 *
3034 * @throws \CiviCRM_API3_Exception
8aeeea00
EH
3035 * @see https://issues.civicrm.org/jira/browse/CRM-20308
3036 */
3cf1fae9 3037 public static function getReceiptFrom($activityID) {
3038 $name = $address = NULL;
3039
4c981f37
MW
3040 if (!empty($activityID) && (Civi::settings()->get('allow_mail_from_logged_in_contact'))) {
3041 // This breaks SPF/DMARC if email is sent from an email address that the server is not authorised to send from.
3042 // so we can disable this behaviour with the "allow_mail_from_logged_in_contact" setting.
3cf1fae9 3043 // There is always a 'Added by' contact for a activity,
3044 // so we can safely use ActivityContact.Getvalue API
fc9f05e0 3045 $sourceContactId = civicrm_api3('ActivityContact', 'getvalue', [
3cf1fae9 3046 'activity_id' => $activityID,
3047 'record_type_id' => 'Activity Source',
3048 'return' => 'contact_id',
fc9f05e0 3049 ]);
3cf1fae9 3050 list($name, $address) = CRM_Contact_BAO_Contact_Location::getEmailDetails($sourceContactId);
8aeeea00 3051 }
3cf1fae9 3052
3053 // If 'From' email address not found for Source Activity Contact then
3054 // fetch the email from domain or logged in user.
3055 if (empty($address)) {
3056 list($name, $address) = CRM_Core_BAO_Domain::getDefaultReceiptFrom();
8aeeea00 3057 }
3cf1fae9 3058
8aeeea00
EH
3059 return "$name <$address>";
3060 }
3061
1d6f94ab
CW
3062 /**
3063 * @return array
3064 */
3065 public static function getEntityRefFilters() {
3066 $filters = [
3067 [
3068 'key' => 'case_id.case_type_id',
3069 'value' => ts('Case Type'),
3070 'entity' => 'Case',
3071 ],
3072 [
3073 'key' => 'case_id.status_id',
3074 'value' => ts('Case Status'),
3075 'entity' => 'Case',
3076 ],
3077 ];
3078 foreach (CRM_Contact_BAO_Contact::getEntityRefFilters() as $filter) {
2229cf4f 3079 $filter += ['entity' => 'Contact'];
1d6f94ab
CW
3080 $filter['key'] = 'contact_id.' . $filter['key'];
3081 $filters[] = $filter;
3082 }
3083 return $filters;
3084 }
3085
41cf58d3
AF
3086 /**
3087 * Fetch Case Role direction from Case Type
3088 */
3089 public static function getCaseRoleDirection($caseId, $roleTypeId = NULL) {
3090 try {
fc9f05e0 3091 $case = civicrm_api3('Case', 'getsingle', ['id' => $caseId]);
41cf58d3
AF
3092 }
3093 catch (CiviCRM_API3_Exception $e) {
3094 // Lack of permissions will throw an exception
3095 return 0;
3096 }
3097 if (!empty($case['case_type_id'])) {
3098 try {
fc9f05e0 3099 $caseType = civicrm_api3('CaseType', 'getsingle', ['id' => $case['case_type_id'], 'return' => ['definition']]);
41cf58d3
AF
3100 }
3101 catch (CiviCRM_API3_Exception $e) {
3102 // Lack of permissions will throw an exception
3103 return 'no case type found';
3104 }
3105 if (!empty($caseType['definition']['caseRoles'])) {
fc9f05e0 3106 $caseRoles = [];
41cf58d3
AF
3107 foreach ($caseType['definition']['caseRoles'] as $key => $roleDetails) {
3108 // Check if its an a_b label
3109 try {
fc9f05e0 3110 $relType = civicrm_api3('RelationshipType', 'getsingle', ['label_a_b' => $roleDetails['name']]);
41cf58d3
AF
3111 }
3112 catch (CiviCRM_API3_Exception $e) {
3113 }
3114 if (!empty($relType['id'])) {
3115 $roleDetails['id'] = $relType['id'];
d0a94888 3116 $roleDetails['direction'] = 'b_a';
41cf58d3
AF
3117 }
3118 // Check if its a b_a label
3119 try {
fc9f05e0 3120 $relTypeBa = civicrm_api3('RelationshipType', 'getsingle', ['label_b_a' => $roleDetails['name']]);
41cf58d3
AF
3121 }
3122 catch (CiviCRM_API3_Exception $e) {
3123 }
3124 if (!empty($relTypeBa['id'])) {
3125 if (!empty($roleDetails['direction'])) {
3126 $roleDetails['direction'] = 'bidrectional';
3127 }
3128 else {
3129 $roleDetails['id'] = $relTypeBa['id'];
d0a94888 3130 $roleDetails['direction'] = 'a_b';
41cf58d3
AF
3131 }
3132 }
3133 $caseRoles[$roleDetails['id']] = $roleDetails;
3134 }
3135 }
3136 return $caseRoles;
3137 }
3138 }
3139
6a488035 3140}