Merge pull request #16835 from colemanw/removeOldStuff
[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');
513 $userID = CRM_Core_Session::singleton()->get('userID');
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)) {
5f1c8c57 634 $casesList[$key]['date'] .= sprintf('<a class="action-item crm-hover-button" href="%s" title="%s"><i class="crm-i fa-pencil"></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
5f1c8c57 673 $userID = CRM_Core_Session::singleton()->get('userID');
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 = [];
41cf58d3 1144 $query = <<<HERESQL
c50e15c8 1145 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
1146 FROM civicrm_relationship cr
1147 JOIN civicrm_relationship_type crt
1148 ON crt.id = cr.relationship_type_id
1149 JOIN civicrm_contact cc
1150 ON cc.id = cr.contact_id_a
1151 AND cc.is_deleted <> 1
1152 LEFT JOIN civicrm_email ce
1153 ON ce.contact_id = cc.id
1154 AND ce.is_primary= 1
1155 LEFT JOIN civicrm_phone cp
1156 ON cp.contact_id = cc.id
1157 AND cp.is_primary= 1
1158 WHERE cr.case_id = %1
1159 AND cr.is_active
1160 AND cc.id NOT IN (%2)
1161 UNION
c50e15c8 1162 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
1163 FROM civicrm_relationship cr
1164 JOIN civicrm_relationship_type crt
1165 ON crt.id = cr.relationship_type_id
1166 JOIN civicrm_contact cc
1167 ON cc.id = cr.contact_id_b
1168 AND cc.is_deleted <> 1
1169 LEFT JOIN civicrm_email ce
1170 ON ce.contact_id = cc.id
1171 AND ce.is_primary= 1
1172 LEFT JOIN civicrm_phone cp
1173 ON cp.contact_id = cc.id
1174 AND cp.is_primary= 1
1175 WHERE cr.case_id = %1
1176 AND cr.is_active
1177 AND cc.id NOT IN (%2)
1178HERESQL;
fc9f05e0 1179 $params = [
1180 1 => [$caseID, 'Integer'],
1181 2 => [implode(',', $caseInfo['client_id']), 'String'],
1182 ];
6a488035
TO
1183 $dao = CRM_Core_DAO::executeQuery($query, $params);
1184
1185 while ($dao->fetch()) {
b982dca0 1186 if (!$includeDetails) {
6a488035
TO
1187 $values[$dao->id] = 1;
1188 }
1189 else {
fc9f05e0 1190 $details = [
6a488035
TO
1191 'contact_id' => $dao->id,
1192 'display_name' => $dao->name,
1193 'sort_name' => $dao->sort_name,
69f9c562 1194 'relationship_type_id' => $dao->relationship_type_id,
6a488035
TO
1195 'role' => $dao->role,
1196 'email' => $dao->email,
a0c7081b 1197 'phone' => $dao->phone,
fc9f05e0 1198 ];
b982dca0 1199 // Add more info about the role (creator, manager)
c50e15c8 1200 // The XML historically has the reverse direction, so look up reverse.
9c1bc317 1201 $role = $caseRoles[$dao->role_name_reverse] ?? NULL;
69f9c562
CW
1202 if ($role) {
1203 unset($role['name']);
1204 $details += $role;
1205 }
1206 $values[] = $details;
6a488035
TO
1207 }
1208 }
6a488035
TO
1209
1210 return $values;
1211 }
1212
1213 /**
100fef9d 1214 * Send e-mail copy of activity
6a488035 1215 *
100fef9d 1216 * @param int $clientId
64bd5a0e
TO
1217 * @param int $activityId
1218 * Activity Id.
1219 * @param array $contacts
1220 * Array of related contact.
6a488035 1221 *
77b97be7 1222 * @param null $attachments
100fef9d 1223 * @param int $caseId
77b97be7 1224 *
67d19299 1225 * @return bool |array
6a488035 1226 */
00be9182 1227 public static function sendActivityCopy($clientId, $activityId, $contacts, $attachments = NULL, $caseId) {
6a488035 1228 if (!$activityId) {
67d19299 1229 return FALSE;
6a488035
TO
1230 }
1231
fc9f05e0 1232 $tplParams = $activityInfo = [];
8d04ae48
AP
1233 $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, 'activity_type_id');
1234 // If it's a case activity
6a488035 1235 if ($caseId) {
6a488035 1236 $nonCaseActivityTypes = CRM_Core_PseudoConstant::activityType();
a7488080 1237 if (!empty($nonCaseActivityTypes[$activityTypeId])) {
6a488035
TO
1238 $anyActivity = TRUE;
1239 }
1240 else {
1241 $anyActivity = FALSE;
1242 }
1243 $tplParams['isCaseActivity'] = 1;
1244 $tplParams['client_id'] = $clientId;
1245 }
1246 else {
1247 $anyActivity = TRUE;
1248 }
1249
1250 $xmlProcessorProcess = new CRM_Case_XMLProcessor_Process();
1251 $isRedact = $xmlProcessorProcess->getRedactActivityEmail();
1252
1253 $xmlProcessorReport = new CRM_Case_XMLProcessor_Report();
1254
1255 $activityInfo = $xmlProcessorReport->getActivityInfo($clientId, $activityId, $anyActivity, $isRedact);
1256 if ($caseId) {
fc9f05e0 1257 $activityInfo['fields'][] = ['label' => 'Case ID', 'type' => 'String', 'value' => $caseId];
6a488035 1258 }
8d04ae48 1259 $tplParams['activityTypeName'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_DAO_Activity', 'activity_type_id', $activityTypeId);
6a488035
TO
1260 $tplParams['activity'] = $activityInfo;
1261 foreach ($tplParams['activity']['fields'] as $k => $val) {
1262 if (CRM_Utils_Array::value('label', $val) == ts('Subject')) {
1263 $activitySubject = $val['value'];
1264 break;
1265 }
1266 }
1267 $session = CRM_Core_Session::singleton();
1268 // CRM-8926 If user is not logged in, use the activity creator as userID
1269 if (!($userID = $session->get('userID'))) {
4322672b 1270 $userID = CRM_Activity_BAO_Activity::getSourceContactID($activityId);
6a488035
TO
1271 }
1272
1273 //also create activities simultaneously of this copy.
fc9f05e0 1274 $activityParams = [];
6a488035
TO
1275
1276 $activityParams['source_record_id'] = $activityId;
1277 $activityParams['source_contact_id'] = $userID;
95a718e1 1278 $activityParams['activity_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_type_id', 'Email');
6a488035 1279 $activityParams['activity_date_time'] = date('YmdHis');
95a718e1
JP
1280 $activityParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_status_id', 'Completed');
1281 $activityParams['medium_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'encounter_medium', 'email');
6a488035
TO
1282 $activityParams['case_id'] = $caseId;
1283 $activityParams['is_auto'] = 0;
1284 $activityParams['target_id'] = $clientId;
1285
1286 $tplParams['activitySubject'] = $activitySubject;
1287
1288 // if it’s a case activity, add hashed id to the template (CRM-5916)
1289 if ($caseId) {
1290 $tplParams['idHash'] = substr(sha1(CIVICRM_SITE_KEY . $caseId), 0, 7);
1291 }
1292
fc9f05e0 1293 $result = [];
8aeeea00
EH
1294 // CRM-20308 get receiptFrom defaults see https://issues.civicrm.org/jira/browse/CRM-20308
1295 $receiptFrom = self::getReceiptFrom($activityId);
6a488035 1296
fc9f05e0 1297 $recordedActivityParams = [];
6a488035
TO
1298
1299 foreach ($contacts as $mail => $info) {
1300 $tplParams['contact'] = $info;
1301 self::buildPermissionLinks($tplParams, $activityParams);
1302
9c1bc317 1303 $displayName = $info['display_name'] ?? NULL;
6a488035 1304
c6327d7d 1305 list($result[CRM_Utils_Array::value('contact_id', $info)], $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate(
fc9f05e0 1306 [
6a488035
TO
1307 'groupName' => 'msg_tpl_workflow_case',
1308 'valueName' => 'case_activity',
6b409353 1309 'contactId' => $info['contact_id'] ?? NULL,
6a488035
TO
1310 'tplParams' => $tplParams,
1311 'from' => $receiptFrom,
1312 'toName' => $displayName,
1313 'toEmail' => $mail,
1314 'attachments' => $attachments,
fc9f05e0 1315 ]
6a488035
TO
1316 );
1317
898f01ea 1318 $activityParams['subject'] = ts('%1 - copy sent to %2', [1 => $activitySubject, 2 => $displayName]);
6a488035
TO
1319 $activityParams['details'] = $message;
1320
21827a41 1321 if (!empty($result[$info['contact_id']])) {
6a488035
TO
1322 /*
1323 * Really only need to record one activity with all the targets combined.
1324 * Originally the template was going to possibly have different content, e.g. depending on permissions,
1325 * but it's always the same content at the moment.
1326 */
1327 if (empty($recordedActivityParams)) {
1328 $recordedActivityParams = $activityParams;
1329 }
1330 else {
1331 $recordedActivityParams['subject'] .= "; $displayName";
1332 }
1333 $recordedActivityParams['target_contact_id'][] = $info['contact_id'];
1334 }
1335 else {
21827a41 1336 unset($result[CRM_Utils_Array::value('contact_id', $info)]);
6a488035
TO
1337 }
1338 }
1339
1340 if (!empty($recordedActivityParams)) {
1341 $activity = CRM_Activity_BAO_Activity::create($recordedActivityParams);
1342
1343 //create case_activity record if its case activity.
1344 if ($caseId) {
fc9f05e0 1345 $caseParams = [
6a488035
TO
1346 'activity_id' => $activity->id,
1347 'case_id' => $caseId,
fc9f05e0 1348 ];
6a488035
TO
1349 self::processCaseActivity($caseParams);
1350 }
1351 }
1352
1353 return $result;
1354 }
1355
1356 /**
1357 * Retrieve count of activities having a particular type, and
1358 * associated with a particular case.
1359 *
64bd5a0e
TO
1360 * @param int $caseId
1361 * ID of the case.
1362 * @param int $activityTypeId
1363 * ID of the activity type.
6a488035
TO
1364 *
1365 * @return array
6a488035 1366 */
00be9182 1367 public static function getCaseActivityCount($caseId, $activityTypeId) {
fc9f05e0 1368 $queryParam = [
1369 1 => [$caseId, 'Integer'],
1370 2 => [$activityTypeId, 'Integer'],
1371 ];
6a488035
TO
1372 $query = "SELECT count(ca.id) as countact
1373 FROM civicrm_activity ca
1374 INNER JOIN civicrm_case_activity cca ON ca.id = cca.activity_id
1375 WHERE ca.activity_type_id = %2
1376 AND cca.case_id = %1
1377 AND ca.is_deleted = 0";
1378
1379 $dao = CRM_Core_DAO::executeQuery($query, $queryParam);
1380 if ($dao->fetch()) {
1381 return $dao->countact;
1382 }
1383
1384 return FALSE;
1385 }
1386
6a488035 1387 /**
d2e5d2ce 1388 * Retrieve the scheduled activity type and date.
6a488035 1389 *
64bd5a0e
TO
1390 * @param array $cases
1391 * Array of contact and case id.
77b97be7
EM
1392 *
1393 * @param string $type
6a488035 1394 *
a6c01b45
CW
1395 * @return array
1396 * Array of scheduled activity type and date
6a488035 1397 *
6a488035 1398 *
6a488035 1399 */
00be9182 1400 public static function getNextScheduledActivity($cases, $type = 'upcoming') {
6a488035
TO
1401 $session = CRM_Core_Session::singleton();
1402 $userID = $session->get('userID');
1403
1404 $caseID = implode(',', $cases['case_id']);
1405 $contactID = implode(',', $cases['contact_id']);
1406
5f1c8c57 1407 $condition = " civicrm_case_contact.contact_id IN( {$contactID} )
6a488035
TO
1408 AND civicrm_case.id IN( {$caseID})
1409 AND civicrm_case.is_deleted = {$cases['case_deleted']}";
1410
2ff620ae 1411 $query = self::getCaseActivityQuery($type, $userID, $condition);
57d38398 1412 $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id');
6a488035 1413
33621c4f 1414 $res = CRM_Core_DAO::executeQuery($query);
6a488035 1415
fc9f05e0 1416 $activityInfo = [];
6a488035
TO
1417 while ($res->fetch()) {
1418 if ($type == 'upcoming') {
2c783cce 1419 $activityInfo[$res->case_id]['date'] = $res->activity_date_time;
9c1bc317 1420 $activityInfo[$res->case_id]['type'] = $activityTypes[$res->activity_type_id] ?? NULL;
6a488035
TO
1421 }
1422 else {
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 }
1427
1428 return $activityInfo;
1429 }
1430
1431 /**
d2e5d2ce 1432 * Combine all the exportable fields from the lower levels object.
6a488035 1433 *
a6c01b45
CW
1434 * @return array
1435 * array of exportable Fields
6a488035 1436 */
00be9182 1437 public static function &exportableFields() {
6a488035
TO
1438 if (!self::$_exportableFields) {
1439 if (!self::$_exportableFields) {
fc9f05e0 1440 self::$_exportableFields = [];
6a488035
TO
1441 }
1442
e96f025a 1443 $fields = CRM_Case_DAO_Case::export();
fc9f05e0 1444 $fields['case_role'] = ['title' => ts('Role in Case')];
1445 $fields['case_type'] = [
e96f025a 1446 'title' => ts('Case Type'),
6a488035 1447 'name' => 'case_type',
fc9f05e0 1448 ];
1449 $fields['case_status'] = [
e96f025a 1450 'title' => ts('Case Status'),
6a488035 1451 'name' => 'case_status',
fc9f05e0 1452 ];
6a488035 1453
e75182f2
JJ
1454 // add custom data for cases
1455 $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Case'));
1456
6a488035
TO
1457 self::$_exportableFields = $fields;
1458 }
1459 return self::$_exportableFields;
1460 }
1461
1462 /**
d2e5d2ce 1463 * Restore the record that are associated with this case.
6a488035 1464 *
64bd5a0e
TO
1465 * @param int $caseId
1466 * Id of the case to restore.
6a488035 1467 *
72b3a70c 1468 * @return bool
6a488035 1469 */
00be9182 1470 public static function restoreCase($caseId) {
6a488035
TO
1471 //restore activities
1472 $activities = self::getCaseActivityDates($caseId);
1473 if ($activities) {
1474 foreach ($activities as $value) {
1475 CRM_Activity_BAO_Activity::restoreActivity($value);
1476 }
1477 }
1478 //restore case
e96f025a 1479 $case = new CRM_Case_DAO_Case();
1480 $case->id = $caseId;
6a488035
TO
1481 $case->is_deleted = 0;
1482 $case->save();
1483
1484 //CRM-7364, enable relationships
1485 self::enableDisableCaseRelationships($caseId, TRUE);
1486 return TRUE;
1487 }
1488
4c6ce474
EM
1489 /**
1490 * @param $groupInfo
1491 * @param null $sort
1492 * @param null $showLinks
1493 * @param bool $returnOnlyCount
1494 * @param int $offset
1495 * @param int $rowCount
1496 *
1497 * @return array
1498 */
00be9182 1499 public static function getGlobalContacts(&$groupInfo, $sort = NULL, $showLinks = NULL, $returnOnlyCount = FALSE, $offset = 0, $rowCount = 25) {
fc9f05e0 1500 $globalContacts = [];
6a488035
TO
1501
1502 $settingsProcessor = new CRM_Case_XMLProcessor_Settings();
1503 $settings = $settingsProcessor->run();
1504 if (!empty($settings)) {
1505 $groupInfo['name'] = $settings['groupname'];
1506 if ($groupInfo['name']) {
fc9f05e0 1507 $searchParams = ['name' => $groupInfo['name']];
1508 $results = [];
6a488035
TO
1509 CRM_Contact_BAO_Group::retrieve($searchParams, $results);
1510 if ($results) {
e96f025a 1511 $groupInfo['id'] = $results['id'];
6a488035 1512 $groupInfo['title'] = $results['title'];
fc9f05e0 1513 $params = [['group', '=', $groupInfo['id'], 0, 0]];
1514 $return = ['contact_id' => 1, 'sort_name' => 1, 'display_name' => 1, 'email' => 1, 'phone' => 1];
f9ff6700 1515 list($globalContacts) = CRM_Contact_BAO_Query::apiQuery($params, $return, NULL, $sort, $offset, $rowCount, TRUE, $returnOnlyCount, FALSE);
6a488035
TO
1516
1517 if ($returnOnlyCount) {
1518 return $globalContacts;
1519 }
1520
1521 if ($showLinks) {
e96f025a 1522 foreach ($globalContacts as $idx => $contact) {
d79c94d5 1523 $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
1524 }
1525 }
1526 }
1527 }
1528 }
1529 return $globalContacts;
1530 }
1531
4c6ce474 1532 /**
d2e5d2ce 1533 * Convenience function to get both case contacts and global in one array.
fc9f05e0 1534 *
100fef9d 1535 * @param int $caseId
4c6ce474
EM
1536 *
1537 * @return array
1538 */
00be9182 1539 public static function getRelatedAndGlobalContacts($caseId) {
6a488035
TO
1540 $relatedContacts = self::getRelatedContacts($caseId);
1541
fc9f05e0 1542 $groupInfo = [];
6a488035
TO
1543 $globalContacts = self::getGlobalContacts($groupInfo);
1544
1545 //unset values which are not required.
1546 foreach ($globalContacts as $k => & $v) {
1547 unset($v['email_id']);
1548 unset($v['group_contact_id']);
1549 unset($v['status']);
1550 unset($v['phone']);
1551 $v['role'] = $groupInfo['title'];
1552 }
1553 //include multiple listings for the same contact/different roles.
1554 $relatedGlobalContacts = array_merge($relatedContacts, $globalContacts);
1555 return $relatedGlobalContacts;
1556 }
1557
1558 /**
100fef9d 1559 * Get Case ActivitiesDueDates with given criteria.
6a488035 1560 *
64bd5a0e
TO
1561 * @param int $caseID
1562 * Case id.
1563 * @param array $criteriaParams
1564 * Given criteria.
1565 * @param bool $latestDate
72b3a70c 1566 * If set newest or oldest date is selected.
6a488035 1567 *
72b3a70c
CW
1568 * @return array
1569 * case activities due dates
6a488035 1570 *
6a488035 1571 */
fc9f05e0 1572 public static function getCaseActivityDates($caseID, $criteriaParams = [], $latestDate = FALSE) {
1573 $values = [];
6a488035 1574 $selectDate = " ca.activity_date_time";
e96f025a 1575 $where = $groupBy = ' ';
6a488035
TO
1576
1577 if (!$caseID) {
408b79bf 1578 return NULL;
6a488035
TO
1579 }
1580
1581 if ($latestDate) {
a7488080 1582 if (!empty($criteriaParams['activity_type_id'])) {
6a488035
TO
1583 $where .= " AND ca.activity_type_id = " . CRM_Utils_Type::escape($criteriaParams['activity_type_id'], 'Integer');
1584 $where .= " AND ca.is_current_revision = 1";
e5cceea5 1585 $groupBy .= " GROUP BY ca.activity_type_id, ca.id";
6a488035
TO
1586 }
1587
a7488080 1588 if (!empty($criteriaParams['newest'])) {
6a488035
TO
1589 $selectDate = " max(ca.activity_date_time) ";
1590 }
1591 else {
1592 $selectDate = " min(ca.activity_date_time) ";
1593 }
1594 }
1595
1596 $query = "SELECT ca.id, {$selectDate} as activity_date
1597 FROM civicrm_activity ca
1598 LEFT JOIN civicrm_case_activity cca ON cca.activity_id = ca.id LEFT JOIN civicrm_case cc ON cc.id = cca.case_id
1599 WHERE cc.id = %1 {$where} {$groupBy}";
1600
fc9f05e0 1601 $params = [1 => [$caseID, 'Integer']];
6a488035
TO
1602 $dao = CRM_Core_DAO::executeQuery($query, $params);
1603
1604 while ($dao->fetch()) {
1605 $values[$dao->id]['id'] = $dao->id;
1606 $values[$dao->id]['activity_date'] = $dao->activity_date;
1607 }
6a488035
TO
1608 return $values;
1609 }
1610
1611 /**
100fef9d 1612 * Create activities when Case or Other roles assigned/modified/deleted.
6a488035 1613 *
100fef9d 1614 * @param int $caseId
64bd5a0e
TO
1615 * @param int $relationshipId
1616 * Relationship id.
1617 * @param int $relContactId
1618 * Case role assignee contactId.
100fef9d 1619 * @param int $contactId
6a488035 1620 */
00be9182 1621 public static function createCaseRoleActivity($caseId, $relationshipId, $relContactId = NULL, $contactId = NULL) {
6a488035
TO
1622 if (!$caseId || !$relationshipId || empty($relationshipId)) {
1623 return;
1624 }
1625
fc9f05e0 1626 $queryParam = [];
6a488035
TO
1627 if (is_array($relationshipId)) {
1628 $relationshipId = implode(',', $relationshipId);
1629 $relationshipClause = " civicrm_relationship.id IN ($relationshipId)";
1630 }
1631 else {
1632 $relationshipClause = " civicrm_relationship.id = %1";
fc9f05e0 1633 $queryParam[1] = [$relationshipId, 'Positive'];
6a488035
TO
1634 }
1635
1636 $query = "
1637 SELECT cc.display_name as clientName,
1638 cca.display_name as assigneeContactName,
1639 civicrm_relationship.case_id as caseId,
1640 civicrm_relationship_type.label_a_b as relation_a_b,
1641 civicrm_relationship_type.label_b_a as relation_b_a,
1642 civicrm_relationship.contact_id_b as rel_contact_id,
1643 civicrm_relationship.contact_id_a as assign_contact_id
1644 FROM civicrm_relationship_type, civicrm_relationship
1645 LEFT JOIN civicrm_contact cc ON cc.id = civicrm_relationship.contact_id_b
1646 LEFT JOIN civicrm_contact cca ON cca.id = civicrm_relationship.contact_id_a
1647 WHERE civicrm_relationship.relationship_type_id = civicrm_relationship_type.id AND {$relationshipClause}";
1648
1649 $dao = CRM_Core_DAO::executeQuery($query, $queryParam);
1650
1651 while ($dao->fetch()) {
e3b9b4f6
KW
1652 // The assignee is not the client.
1653 if ($dao->rel_contact_id != $contactId) {
6a488035
TO
1654 $caseRelationship = $dao->relation_a_b;
1655 $assigneContactName = $dao->clientName;
1656 $assigneContactIds[$dao->rel_contact_id] = $dao->rel_contact_id;
1657 }
1658 else {
1659 $caseRelationship = $dao->relation_b_a;
1660 $assigneContactName = $dao->assigneeContactName;
1661 $assigneContactIds[$dao->assign_contact_id] = $dao->assign_contact_id;
1662 }
1663 }
1664
1665 $session = CRM_Core_Session::singleton();
fc9f05e0 1666 $activityParams = [
6a488035
TO
1667 'source_contact_id' => $session->get('userID'),
1668 'subject' => $caseRelationship . ' : ' . $assigneContactName,
1669 'activity_date_time' => date('YmdHis'),
9c248a42 1670 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'),
fc9f05e0 1671 ];
6a488035
TO
1672
1673 //if $relContactId is passed, role is added or modified.
1674 if (!empty($relContactId)) {
1675 $activityParams['assignee_contact_id'] = $assigneContactIds;
9c248a42 1676 $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Assign Case Role');
6a488035
TO
1677 }
1678 else {
9c248a42 1679 $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Remove Case Role');
6a488035
TO
1680 }
1681
1682 $activityParams['activity_type_id'] = $activityTypeID;
1683
1684 $activity = CRM_Activity_BAO_Activity::create($activityParams);
1685
1686 //create case_activity record.
fc9f05e0 1687 $caseParams = [
6a488035
TO
1688 'activity_id' => $activity->id,
1689 'case_id' => $caseId,
fc9f05e0 1690 ];
6a488035
TO
1691
1692 CRM_Case_BAO_Case::processCaseActivity($caseParams);
1693 }
1694
1695 /**
100fef9d 1696 * Get case manger
6a488035
TO
1697 * contact which is assigned a case role of case manager.
1698 *
64bd5a0e
TO
1699 * @param int $caseType
1700 * Case type.
1701 * @param int $caseId
1702 * Case id.
6a488035 1703 *
c00bd201 1704 * @return string
1705 * html hyperlink of manager contact view page
6a488035 1706 *
6a488035 1707 */
00be9182 1708 public static function getCaseManagerContact($caseType, $caseId) {
6a488035 1709 if (!$caseType || !$caseId) {
408b79bf 1710 return NULL;
6a488035
TO
1711 }
1712
5757b086 1713 $caseManagerName = '---';
6a488035
TO
1714 $xmlProcessor = new CRM_Case_XMLProcessor_Process();
1715
1716 $managerRoleId = $xmlProcessor->getCaseManagerRoleId($caseType);
1717
1718 if (!empty($managerRoleId)) {
41cf58d3
AF
1719 if (substr($managerRoleId, -4) == '_a_b') {
1720 $managerRoleQuery = "
1721 SELECT civicrm_contact.id as casemanager_id,
1722 civicrm_contact.sort_name as casemanager
1723 FROM civicrm_contact
1724 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
1725 LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id
1726 WHERE civicrm_case.id = %2 AND is_active = 1";
1727 }
1728 if (substr($managerRoleId, -4) == '_b_a') {
1729 $managerRoleQuery = "
1730 SELECT civicrm_contact.id as casemanager_id,
1731 civicrm_contact.sort_name as casemanager
1732 FROM civicrm_contact
1733 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
1734 LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id
1735 WHERE civicrm_case.id = %2 AND is_active = 1";
1736 }
6a488035 1737
fc9f05e0 1738 $managerRoleParams = [
1739 1 => [substr($managerRoleId, 0, -4), 'Integer'],
1740 2 => [$caseId, 'Integer'],
1741 ];
6a488035
TO
1742
1743 $dao = CRM_Core_DAO::executeQuery($managerRoleQuery, $managerRoleParams);
1744 if ($dao->fetch()) {
5757b086 1745 $caseManagerName = sprintf('<a href="%s">%s</a>',
fc9f05e0 1746 CRM_Utils_System::url('civicrm/contact/view', ['cid' => $dao->casemanager_id]),
c00bd201 1747 $dao->casemanager
1748 );
6a488035
TO
1749 }
1750 }
5757b086 1751
1752 return $caseManagerName;
6a488035
TO
1753 }
1754
4c6ce474 1755 /**
100fef9d 1756 * @param int $contactId
4c6ce474
EM
1757 * @param bool $excludeDeleted
1758 *
eeb45e43 1759 * @return int
4c6ce474 1760 */
00be9182 1761 public static function caseCount($contactId = NULL, $excludeDeleted = TRUE) {
fc9f05e0 1762 $params = ['check_permissions' => TRUE];
6a488035 1763 if ($excludeDeleted) {
0a1a8b63 1764 $params['is_deleted'] = 0;
6a488035
TO
1765 }
1766 if ($contactId) {
0a1a8b63 1767 $params['contact_id'] = $contactId;
6a488035 1768 }
eeb45e43
CW
1769 try {
1770 return civicrm_api3('Case', 'getcount', $params);
1771 }
1772 catch (CiviCRM_API3_Exception $e) {
1773 // Lack of permissions will throw an exception
1774 return 0;
1775 }
6a488035
TO
1776 }
1777
1778 /**
6e19e2ea 1779 * Retrieve related case ids for given case.
6a488035 1780 *
6e19e2ea 1781 * @param int $caseId
64bd5a0e
TO
1782 * @param bool $excludeDeleted
1783 * Do not include deleted cases.
6a488035 1784 *
72b3a70c 1785 * @return array
6a488035 1786 */
6e19e2ea 1787 public static function getRelatedCaseIds($caseId, $excludeDeleted = TRUE) {
6a488035
TO
1788 //FIXME : do check for permissions.
1789
6e19e2ea 1790 if (!$caseId) {
fc9f05e0 1791 return [];
6a488035
TO
1792 }
1793
b864360d 1794 $linkActType = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Link Cases');
6a488035 1795 if (!$linkActType) {
fc9f05e0 1796 return [];
6a488035
TO
1797 }
1798
1799 $whereClause = "mainCase.id = %2";
1800 if ($excludeDeleted) {
1801 $whereClause .= " AND ( relAct.is_deleted = 0 OR relAct.is_deleted IS NULL )";
1802 }
1803
6a488035
TO
1804 $query = "
1805 SELECT relCaseAct.case_id
1806 FROM civicrm_case mainCase
1807 INNER JOIN civicrm_case_activity mainCaseAct ON (mainCaseAct.case_id = mainCase.id)
1808 INNER JOIN civicrm_activity mainAct ON (mainCaseAct.activity_id = mainAct.id AND mainAct.activity_type_id = %1)
1809 INNER JOIN civicrm_case_activity relCaseAct ON (relCaseAct.activity_id = mainAct.id AND mainCaseAct.id != relCaseAct.id)
1810 INNER JOIN civicrm_activity relAct ON (relCaseAct.activity_id = relAct.id AND relAct.activity_type_id = %1)
1811 WHERE $whereClause";
1812
fc9f05e0 1813 $dao = CRM_Core_DAO::executeQuery($query, [
1814 1 => [$linkActType, 'Integer'],
1815 2 => [$caseId, 'Integer'],
1816 ]);
1817 $relatedCaseIds = [];
6a488035
TO
1818 while ($dao->fetch()) {
1819 $relatedCaseIds[$dao->case_id] = $dao->case_id;
1820 }
6a488035 1821
6e19e2ea
CW
1822 return array_values($relatedCaseIds);
1823 }
1824
1825 /**
1826 * Retrieve related case details for given case.
1827 *
1828 * @param int $caseId
1829 * @param bool $excludeDeleted
1830 * Do not include deleted cases.
1831 *
1832 * @return array
1833 */
1834 public static function getRelatedCases($caseId, $excludeDeleted = TRUE) {
1835 $relatedCaseIds = self::getRelatedCaseIds($caseId, $excludeDeleted);
fc9f05e0 1836 $relatedCases = [];
6e19e2ea
CW
1837
1838 if (!$relatedCaseIds) {
fc9f05e0 1839 return [];
6a488035
TO
1840 }
1841
1842 $whereClause = 'relCase.id IN ( ' . implode(',', $relatedCaseIds) . ' )';
1843 if ($excludeDeleted) {
1844 $whereClause .= " AND ( relCase.is_deleted = 0 OR relCase.is_deleted IS NULL )";
1845 }
1846
1847 //filter for permissioned cases.
fc9f05e0 1848 $filterCases = [];
6a488035
TO
1849 $doFilterCases = FALSE;
1850 if (!CRM_Core_Permission::check('access all cases and activities')) {
1851 $doFilterCases = TRUE;
5f1c8c57 1852 $filterCases = CRM_Case_BAO_Case::getCases(FALSE);
6a488035
TO
1853 }
1854
1855 //2. fetch the details of related cases.
1856 $query = "
1857 SELECT relCase.id as id,
8ffdec17 1858 civicrm_case_type.title as case_type,
6a488035 1859 client.display_name as client_name,
74b15fae
CR
1860 client.id as client_id,
1861 relCase.status_id
6a488035
TO
1862 FROM civicrm_case relCase
1863 INNER JOIN civicrm_case_contact relCaseContact ON ( relCase.id = relCaseContact.case_id )
1864 INNER JOIN civicrm_contact client ON ( client.id = relCaseContact.contact_id )
8ffdec17 1865 LEFT JOIN civicrm_case_type ON relCase.case_type_id = civicrm_case_type.id
6a488035
TO
1866 WHERE {$whereClause}";
1867
e96f025a 1868 $dao = CRM_Core_DAO::executeQuery($query);
6a488035
TO
1869 $contactViewUrl = CRM_Utils_System::url("civicrm/contact/view", "reset=1&cid=");
1870 $hasViewContact = CRM_Core_Permission::giveMeAllACLs();
74b15fae 1871 $statuses = CRM_Case_BAO_Case::buildOptions('status_id');
6a488035
TO
1872
1873 while ($dao->fetch()) {
1874 $caseView = NULL;
1875 if (!$doFilterCases || array_key_exists($dao->id, $filterCases)) {
1876 $caseViewStr = "reset=1&id={$dao->id}&cid={$dao->client_id}&action=view&context=case&selectedChild=case";
1877 $caseViewUrl = CRM_Utils_System::url("civicrm/contact/view/case", $caseViewStr);
6ce08914 1878 $caseView = "<a class='action-item no-popup crm-hover-button' href='{$caseViewUrl}'>" . ts('View Case') . "</a>";
6a488035
TO
1879 }
1880 $clientView = $dao->client_name;
1881 if ($hasViewContact) {
1882 $clientView = "<a href='{$contactViewUrl}{$dao->client_id}'>$dao->client_name</a>";
1883 }
1884
fc9f05e0 1885 $relatedCases[$dao->id] = [
6a488035
TO
1886 'case_id' => $dao->id,
1887 'case_type' => $dao->case_type,
1888 'client_name' => $clientView,
74b15fae 1889 'case_status' => $statuses[$dao->status_id],
6a488035 1890 'links' => $caseView,
fc9f05e0 1891 ];
6a488035 1892 }
6a488035
TO
1893
1894 return $relatedCases;
1895 }
1896
1897 /**
1898 * Merge two duplicate contacts' cases - follow CRM-5758 rules.
1899 *
fc9f05e0 1900 * @param int $mainContactId
1901 * @param int $otherContactId
1902 *
6a488035
TO
1903 * @see CRM_Dedupe_Merger::cpTables()
1904 *
1905 * TODO: use the 3rd $sqls param to append sql statements rather than executing them here
cde2037d 1906 *
6a488035 1907 */
00be9182 1908 public static function mergeContacts($mainContactId, $otherContactId) {
6a488035
TO
1909 self::mergeCases($mainContactId, NULL, $otherContactId);
1910 }
1911
1912 /**
1913 * Function perform two task.
1914 * 1. Merge two duplicate contacts cases - follow CRM-5758 rules.
1915 * 2. Merge two cases of same contact - follow CRM-5598 rules.
1916 *
64bd5a0e
TO
1917 * @param int $mainContactId
1918 * Contact id of main contact record.
1919 * @param int $mainCaseId
1920 * Case id of main case record.
1921 * @param int $otherContactId
1922 * Contact id of record which is going to merge.
1923 * @param int $otherCaseId
1924 * Case id of record which is going to merge.
77b97be7
EM
1925 *
1926 * @param bool $changeClient
6a488035 1927 *
e97c66ff 1928 * @return int|null
1929 * @throws \CRM_Core_Exception
6a488035 1930 */
a130e045 1931 public static function mergeCases(
28d4d481
TO
1932 $mainContactId, $mainCaseId = NULL, $otherContactId = NULL,
1933 $otherCaseId = NULL, $changeClient = FALSE) {
6a488035
TO
1934 $moveToTrash = TRUE;
1935
1936 $duplicateContacts = FALSE;
1937 if ($mainContactId && $otherContactId &&
1938 $mainContactId != $otherContactId
1939 ) {
1940 $duplicateContacts = TRUE;
1941 }
1942
1943 $duplicateCases = FALSE;
1944 if ($mainCaseId && $otherCaseId &&
1945 $mainCaseId != $otherCaseId
1946 ) {
1947 $duplicateCases = TRUE;
1948 }
1949
fc9f05e0 1950 $mainCaseIds = [];
6a488035
TO
1951 if (!$duplicateContacts && !$duplicateCases) {
1952 return $mainCaseIds;
1953 }
1954
b864360d
MWMC
1955 $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate');
1956 $completedActivityStatus = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed');
44f817d4 1957 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
9e74e3ce 1958 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
1959 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
1960 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
8ef12e64 1961
fc9f05e0 1962 $processCaseIds = [$otherCaseId];
6a488035
TO
1963 if ($duplicateContacts && !$duplicateCases) {
1964 if ($changeClient) {
fc9f05e0 1965 $processCaseIds = [$mainCaseId];
6a488035
TO
1966 }
1967 else {
1968 //get all case ids for other contact.
1969 $processCaseIds = self::retrieveCaseIdsByContactId($otherContactId, TRUE);
1970 }
1971 if (!is_array($processCaseIds)) {
1972 return;
1973 }
1974 }
1975
1976 $session = CRM_Core_Session::singleton();
1977 $currentUserId = $session->get('userID');
1978
02094cdb
JJ
1979 CRM_Utils_Hook::pre_case_merge($mainContactId, $mainCaseId, $otherContactId, $otherCaseId, $changeClient);
1980
6a488035
TO
1981 // copy all cases and connect to main contact id.
1982 foreach ($processCaseIds as $otherCaseId) {
1983 if ($duplicateContacts) {
fc9f05e0 1984 $mainCase = CRM_Core_DAO::copyGeneric('CRM_Case_DAO_Case', ['id' => $otherCaseId]);
6a488035
TO
1985 $mainCaseId = $mainCase->id;
1986 if (!$mainCaseId) {
1987 continue;
1988 }
8bd86283 1989
6a488035
TO
1990 $mainCaseIds[] = $mainCaseId;
1991 //insert record for case contact.
1992 $otherCaseContact = new CRM_Case_DAO_CaseContact();
1993 $otherCaseContact->case_id = $otherCaseId;
1994 $otherCaseContact->find();
1995 while ($otherCaseContact->fetch()) {
1996 $mainCaseContact = new CRM_Case_DAO_CaseContact();
1997 $mainCaseContact->case_id = $mainCaseId;
1998 $mainCaseContact->contact_id = $otherCaseContact->contact_id;
1999 if ($mainCaseContact->contact_id == $otherContactId) {
2000 $mainCaseContact->contact_id = $mainContactId;
2001 }
2002 //avoid duplicate object.
2003 if (!$mainCaseContact->find(TRUE)) {
2004 $mainCaseContact->save();
2005 }
6a488035 2006 }
6a488035
TO
2007 }
2008 elseif (!$otherContactId) {
2009 $otherContactId = $mainContactId;
2010 }
2011
2012 if (!$mainCaseId || !$otherCaseId ||
2013 !$mainContactId || !$otherContactId
2014 ) {
2015 continue;
2016 }
2017
2018 // get all activities for other case.
fc9f05e0 2019 $otherCaseActivities = [];
6a488035
TO
2020 CRM_Core_DAO::commonRetrieveAll('CRM_Case_DAO_CaseActivity', 'case_id', $otherCaseId, $otherCaseActivities);
2021
2022 //for duplicate cases do not process singleton activities.
fc9f05e0 2023 $otherActivityIds = $singletonActivityIds = [];
6a488035 2024 foreach ($otherCaseActivities as $caseActivityId => $otherIds) {
9c1bc317 2025 $otherActId = $otherIds['activity_id'] ?? NULL;
6a488035
TO
2026 if (!$otherActId || in_array($otherActId, $otherActivityIds)) {
2027 continue;
2028 }
2029 $otherActivityIds[] = $otherActId;
2030 }
2031 if ($duplicateCases) {
2032 if ($openCaseType = array_search('Open Case', $activityTypes)) {
2033 $sql = "
2034SELECT id
2035 FROM civicrm_activity
2036 WHERE activity_type_id = $openCaseType
2037 AND id IN ( " . implode(',', array_values($otherActivityIds)) . ');';
2038 $dao = CRM_Core_DAO::executeQuery($sql);
2039 while ($dao->fetch()) {
2040 $singletonActivityIds[] = $dao->id;
2041 }
6a488035
TO
2042 }
2043 }
2044
2045 // migrate all activities and connect to main contact.
fc9f05e0 2046 $copiedActivityIds = $activityMappingIds = [];
6a488035
TO
2047 sort($otherActivityIds);
2048 foreach ($otherActivityIds as $otherActivityId) {
2049
2050 //for duplicate cases -
2051 //do not migrate singleton activities.
2052 if (!$otherActivityId || in_array($otherActivityId, $singletonActivityIds)) {
2053 continue;
2054 }
2055
2056 //migrate activity record.
2057 $otherActivity = new CRM_Activity_DAO_Activity();
2058 $otherActivity->id = $otherActivityId;
2059 if (!$otherActivity->find(TRUE)) {
2060 continue;
2061 }
2062
fc9f05e0 2063 $mainActVals = [];
6a488035
TO
2064 $mainActivity = new CRM_Activity_DAO_Activity();
2065 CRM_Core_DAO::storeValues($otherActivity, $mainActVals);
2066 $mainActivity->copyValues($mainActVals);
2067 $mainActivity->id = NULL;
2068 $mainActivity->activity_date_time = CRM_Utils_Date::isoToMysql($otherActivity->activity_date_time);
6a488035
TO
2069 $mainActivity->source_record_id = CRM_Utils_Array::value($mainActivity->source_record_id,
2070 $activityMappingIds
2071 );
2072
2073 $mainActivity->original_id = CRM_Utils_Array::value($mainActivity->original_id,
2074 $activityMappingIds
2075 );
2076
2077 $mainActivity->parent_id = CRM_Utils_Array::value($mainActivity->parent_id,
2078 $activityMappingIds
2079 );
2080 $mainActivity->save();
2081 $mainActivityId = $mainActivity->id;
2082 if (!$mainActivityId) {
2083 continue;
2084 }
2085
2086 $activityMappingIds[$otherActivityId] = $mainActivityId;
4322672b 2087 // insert log of all activities
6a488035
TO
2088 CRM_Activity_BAO_Activity::logActivityAction($mainActivity);
2089
6a488035
TO
2090 $copiedActivityIds[] = $otherActivityId;
2091
2092 //create case activity record.
2093 $mainCaseActivity = new CRM_Case_DAO_CaseActivity();
2094 $mainCaseActivity->case_id = $mainCaseId;
2095 $mainCaseActivity->activity_id = $mainActivityId;
2096 $mainCaseActivity->save();
6a488035 2097
4322672b 2098 //migrate source activity.
2099 $otherSourceActivity = new CRM_Activity_DAO_ActivityContact();
2100 $otherSourceActivity->activity_id = $otherActivityId;
2101 $otherSourceActivity->record_type_id = $sourceID;
2102 $otherSourceActivity->find();
2103 while ($otherSourceActivity->fetch()) {
2104 $mainActivitySource = new CRM_Activity_DAO_ActivityContact();
2105 $mainActivitySource->record_type_id = $sourceID;
2106 $mainActivitySource->activity_id = $mainActivityId;
2107 $mainActivitySource->contact_id = $otherSourceActivity->contact_id;
2108 if ($mainActivitySource->contact_id == $otherContactId) {
2109 $mainActivitySource->contact_id = $mainContactId;
2110 }
2111 //avoid duplicate object.
2112 if (!$mainActivitySource->find(TRUE)) {
2113 $mainActivitySource->save();
2114 }
4322672b 2115 }
4322672b 2116
6a488035 2117 //migrate target activities.
4e3d3cfc 2118 $otherTargetActivity = new CRM_Activity_DAO_ActivityContact();
6a488035 2119 $otherTargetActivity->activity_id = $otherActivityId;
9e74e3ce 2120 $otherTargetActivity->record_type_id = $targetID;
6a488035
TO
2121 $otherTargetActivity->find();
2122 while ($otherTargetActivity->fetch()) {
4e3d3cfc 2123 $mainActivityTarget = new CRM_Activity_DAO_ActivityContact();
9e74e3ce 2124 $mainActivityTarget->record_type_id = $targetID;
6a488035 2125 $mainActivityTarget->activity_id = $mainActivityId;
00bf7e59 2126 $mainActivityTarget->contact_id = $otherTargetActivity->contact_id;
2127 if ($mainActivityTarget->contact_id == $otherContactId) {
2128 $mainActivityTarget->contact_id = $mainContactId;
6a488035
TO
2129 }
2130 //avoid duplicate object.
2131 if (!$mainActivityTarget->find(TRUE)) {
2132 $mainActivityTarget->save();
2133 }
6a488035 2134 }
6a488035
TO
2135
2136 //migrate assignee activities.
4e3d3cfc 2137 $otherAssigneeActivity = new CRM_Activity_DAO_ActivityContact();
6a488035 2138 $otherAssigneeActivity->activity_id = $otherActivityId;
9e74e3ce 2139 $otherAssigneeActivity->record_type_id = $assigneeID;
6a488035
TO
2140 $otherAssigneeActivity->find();
2141 while ($otherAssigneeActivity->fetch()) {
4e3d3cfc 2142 $mainAssigneeActivity = new CRM_Activity_DAO_ActivityContact();
6a488035 2143 $mainAssigneeActivity->activity_id = $mainActivityId;
9e74e3ce 2144 $mainAssigneeActivity->record_type_id = $assigneeID;
00bf7e59 2145 $mainAssigneeActivity->contact_id = $otherAssigneeActivity->contact_id;
2146 if ($mainAssigneeActivity->contact_id == $otherContactId) {
2147 $mainAssigneeActivity->contact_id = $mainContactId;
6a488035
TO
2148 }
2149 //avoid duplicate object.
2150 if (!$mainAssigneeActivity->find(TRUE)) {
2151 $mainAssigneeActivity->save();
2152 }
6a488035 2153 }
8c31eef5
D
2154
2155 // copy custom fields and attachments
fc9f05e0 2156 $aparams = [
4322672b 2157 'activityID' => $otherActivityId,
2158 'mainActivityId' => $mainActivityId,
fc9f05e0 2159 ];
8c31eef5 2160 CRM_Activity_BAO_Activity::copyExtendedActivityData($aparams);
6a488035
TO
2161 }
2162
2163 //copy case relationship.
2164 if ($duplicateContacts) {
2165 //migrate relationship records.
2166 $otherRelationship = new CRM_Contact_DAO_Relationship();
2167 $otherRelationship->case_id = $otherCaseId;
2168 $otherRelationship->find();
fc9f05e0 2169 $otherRelationshipIds = [];
6a488035 2170 while ($otherRelationship->fetch()) {
fc9f05e0 2171 $otherRelVals = [];
6a488035
TO
2172 $updateOtherRel = FALSE;
2173 CRM_Core_DAO::storeValues($otherRelationship, $otherRelVals);
2174
2175 $mainRelationship = new CRM_Contact_DAO_Relationship();
2176 $mainRelationship->copyValues($otherRelVals);
2177 $mainRelationship->id = NULL;
2178 $mainRelationship->case_id = $mainCaseId;
2179 if ($mainRelationship->contact_id_a == $otherContactId) {
2180 $updateOtherRel = TRUE;
2181 $mainRelationship->contact_id_a = $mainContactId;
2182 }
2183
2184 //case creator change only when we merge user contact.
2185 if ($mainRelationship->contact_id_b == $otherContactId) {
2186 //do not change creator for change client.
2187 if (!$changeClient) {
2188 $updateOtherRel = TRUE;
2189 $mainRelationship->contact_id_b = ($currentUserId) ? $currentUserId : $mainContactId;
2190 }
2191 }
2192 $mainRelationship->end_date = CRM_Utils_Date::isoToMysql($otherRelationship->end_date);
2193 $mainRelationship->start_date = CRM_Utils_Date::isoToMysql($otherRelationship->start_date);
2194
2195 //avoid duplicate object.
2196 if (!$mainRelationship->find(TRUE)) {
2197 $mainRelationship->save();
2198 }
6a488035
TO
2199
2200 //get the other relationship ids to update end date.
2201 if ($updateOtherRel) {
2202 $otherRelationshipIds[$otherRelationship->id] = $otherRelationship->id;
2203 }
2204 }
6a488035
TO
2205
2206 //update other relationships end dates
2207 if (!empty($otherRelationshipIds)) {
2208 $sql = 'UPDATE civicrm_relationship
2209 SET end_date = CURDATE()
2210 WHERE id IN ( ' . implode(',', $otherRelationshipIds) . ')';
2211 CRM_Core_DAO::executeQuery($sql);
2212 }
2213 }
2214
2215 //move other case to trash.
2216 $mergeCase = self::deleteCase($otherCaseId, $moveToTrash);
2217 if (!$mergeCase) {
2218 continue;
2219 }
2220
2221 $mergeActSubject = $mergeActSubjectDetails = $mergeActType = '';
2222 if ($changeClient) {
2223 $mainContactDisplayName = CRM_Contact_BAO_Contact::displayName($mainContactId);
2224 $otherContactDisplayName = CRM_Contact_BAO_Contact::displayName($otherContactId);
2225
2226 $mergeActType = array_search('Reassigned Case', $activityTypes);
2227 $mergeActSubject = ts("Case %1 reassigned client from %2 to %3. New Case ID is %4.",
fc9f05e0 2228 [
e96f025a 2229 1 => $otherCaseId,
2230 2 => $otherContactDisplayName,
2231 3 => $mainContactDisplayName,
21dfd5f5 2232 4 => $mainCaseId,
fc9f05e0 2233 ]
6a488035
TO
2234 );
2235 }
2236 elseif ($duplicateContacts) {
2237 $mergeActType = array_search('Merge Case', $activityTypes);
2238 $mergeActSubject = ts("Case %1 copied from contact id %2 to contact id %3 via merge. New Case ID is %4.",
fc9f05e0 2239 [
e96f025a 2240 1 => $otherCaseId,
2241 2 => $otherContactId,
2242 3 => $mainContactId,
21dfd5f5 2243 4 => $mainCaseId,
fc9f05e0 2244 ]
6a488035
TO
2245 );
2246 }
2247 else {
2248 $mergeActType = array_search('Merge Case', $activityTypes);
fc9f05e0 2249 $mergeActSubject = ts("Case %1 merged into case %2", [1 => $otherCaseId, 2 => $mainCaseId]);
6a488035
TO
2250 if (!empty($copiedActivityIds)) {
2251 $sql = '
2252SELECT id, subject, activity_date_time, activity_type_id
2253FROM civicrm_activity
2254WHERE id IN (' . implode(',', $copiedActivityIds) . ')';
2255 $dao = CRM_Core_DAO::executeQuery($sql);
2256 while ($dao->fetch()) {
2257 $mergeActSubjectDetails .= "{$dao->activity_date_time} :: {$activityTypes[$dao->activity_type_id]}";
2258 if ($dao->subject) {
2259 $mergeActSubjectDetails .= " :: {$dao->subject}";
2260 }
2261 $mergeActSubjectDetails .= "<br />";
2262 }
2263 }
2264 }
2265
b864360d 2266 // Create merge activity record. Source for merge activity is the logged in user's contact ID ($currentUserId).
fc9f05e0 2267 $activityParams = [
6a488035
TO
2268 'subject' => $mergeActSubject,
2269 'details' => $mergeActSubjectDetails,
b864360d 2270 'status_id' => $completedActivityStatus,
6a488035 2271 'activity_type_id' => $mergeActType,
8c677b07 2272 'source_contact_id' => $currentUserId,
6a488035 2273 'activity_date_time' => date('YmdHis'),
fc9f05e0 2274 ];
6a488035
TO
2275
2276 $mergeActivity = CRM_Activity_BAO_Activity::create($activityParams);
2277 $mergeActivityId = $mergeActivity->id;
2278 if (!$mergeActivityId) {
2279 continue;
2280 }
6a488035
TO
2281
2282 //connect merge activity to case.
fc9f05e0 2283 $mergeCaseAct = [
6a488035
TO
2284 'case_id' => $mainCaseId,
2285 'activity_id' => $mergeActivityId,
fc9f05e0 2286 ];
6a488035
TO
2287
2288 self::processCaseActivity($mergeCaseAct);
2289 }
02094cdb
JJ
2290
2291 CRM_Utils_Hook::post_case_merge($mainContactId, $mainCaseId, $otherContactId, $otherCaseId, $changeClient);
2292
6a488035
TO
2293 return $mainCaseIds;
2294 }
2295
2296 /**
2297 * Validate contact permission for
2298 * edit/view on activity record and build links.
2299 *
64bd5a0e
TO
2300 * @param array $tplParams
2301 * Params to be sent to template for sending email.
2302 * @param array $activityParams
2303 * Info of the activity.
6a488035 2304 */
00be9182 2305 public static function buildPermissionLinks(&$tplParams, $activityParams) {
6a488035
TO
2306 $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityParams['source_record_id'],
2307 'activity_type_id', 'id'
2308 );
2309
a7488080 2310 if (!empty($tplParams['isCaseActivity'])) {
6a488035
TO
2311 $tplParams['editActURL'] = CRM_Utils_System::url('civicrm/case/activity',
2312 "reset=1&cid={$activityParams['target_id']}&caseid={$activityParams['case_id']}&action=update&id={$activityParams['source_record_id']}", TRUE
2313 );
2314
2315 $tplParams['viewActURL'] = CRM_Utils_System::url('civicrm/case/activity/view',
2316 "reset=1&aid={$activityParams['source_record_id']}&cid={$activityParams['target_id']}&caseID={$activityParams['case_id']}", TRUE
2317 );
2318
2319 $tplParams['manageCaseURL'] = CRM_Utils_System::url('civicrm/contact/view/case',
2320 "reset=1&id={$activityParams['case_id']}&cid={$activityParams['target_id']}&action=view&context=home", TRUE
2321 );
2322 }
2323 else {
2324 $tplParams['editActURL'] = CRM_Utils_System::url('civicrm/contact/view/activity',
2325 "atype=$activityTypeId&action=update&reset=1&id={$activityParams['source_record_id']}&cid={$tplParams['contact']['contact_id']}&context=activity", TRUE
2326 );
2327
2328 $tplParams['viewActURL'] = CRM_Utils_System::url('civicrm/contact/view/activity',
2329 "atype=$activityTypeId&action=view&reset=1&id={$activityParams['source_record_id']}&cid={$tplParams['contact']['contact_id']}&context=activity", TRUE
2330 );
2331 }
2332 }
2333
2334 /**
2335 * Validate contact permission for
2336 * given operation on activity record.
2337 *
64bd5a0e
TO
2338 * @param int $activityId
2339 * Activity record id.
2340 * @param string $operation
2341 * User operation.
2342 * @param int $actTypeId
2343 * Activity type id.
2344 * @param int $contactId
2345 * Contact id/if not pass consider logged in.
2346 * @param bool $checkComponent
2347 * Do we need to check component enabled.
6a488035 2348 *
a130e045 2349 * @return bool
6a488035 2350 */
00be9182 2351 public static function checkPermission($activityId, $operation, $actTypeId = NULL, $contactId = NULL, $checkComponent = TRUE) {
6a488035
TO
2352 $allow = FALSE;
2353 if (!$actTypeId && $activityId) {
2354 $actTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, 'activity_type_id');
2355 }
2356
2357 if (!$activityId || !$operation || !$actTypeId) {
2358 return $allow;
2359 }
2360
2361 //do check for civicase component enabled.
077dbf5e
CW
2362 if ($checkComponent && !self::enabled()) {
2363 return $allow;
6a488035
TO
2364 }
2365
2366 //do check for cases.
fc9f05e0 2367 $caseActOperations = [
6a488035
TO
2368 'File On Case',
2369 'Link Cases',
2370 'Move To Case',
2371 'Copy To Case',
fc9f05e0 2372 ];
6a488035
TO
2373
2374 if (in_array($operation, $caseActOperations)) {
abd06efc
CW
2375 static $caseCount;
2376 if (!isset($caseCount)) {
eeb45e43 2377 try {
fc9f05e0 2378 $caseCount = civicrm_api3('Case', 'getcount', [
eeb45e43 2379 'check_permissions' => TRUE,
fc9f05e0 2380 'status_id' => ['!=' => 'Closed'],
eeb45e43 2381 'is_deleted' => 0,
fc9f05e0 2382 'end_date' => ['IS NULL' => 1],
2383 ]);
eeb45e43
CW
2384 }
2385 catch (CiviCRM_API3_Exception $e) {
2386 // Lack of permissions will throw an exception
2387 $caseCount = 0;
2388 }
6a488035
TO
2389 }
2390 if ($operation == 'File On Case') {
abd06efc 2391 $allow = !empty($caseCount);
6a488035
TO
2392 }
2393 else {
abd06efc 2394 $allow = ($caseCount > 1);
6a488035
TO
2395 }
2396 }
2397
fc9f05e0 2398 $actionOperations = ['view', 'edit', 'delete'];
6a488035
TO
2399 if (in_array($operation, $actionOperations)) {
2400
2401 //do cache when user has non/supper permission.
2402 static $allowOperations;
2403
2404 if (!is_array($allowOperations) ||
2405 !array_key_exists($operation, $allowOperations)
2406 ) {
2407
2408 if (!$contactId) {
2409 $session = CRM_Core_Session::singleton();
2410 $contactId = $session->get('userID');
2411 }
2412
2413 //check for permissions.
fc9f05e0 2414 $permissions = [
2415 'view' => [
6a488035
TO
2416 'access my cases and activities',
2417 'access all cases and activities',
fc9f05e0 2418 ],
2419 'edit' => [
6a488035
TO
2420 'access my cases and activities',
2421 'access all cases and activities',
fc9f05e0 2422 ],
2423 'delete' => ['delete activities'],
2424 ];
6a488035
TO
2425
2426 //check for core permission.
fc9f05e0 2427 $hasPermissions = [];
9c1bc317 2428 $checkPermissions = $permissions[$operation] ?? NULL;
6a488035
TO
2429 if (is_array($checkPermissions)) {
2430 foreach ($checkPermissions as $per) {
2431 if (CRM_Core_Permission::check($per)) {
2432 $hasPermissions[$operation][] = $per;
2433 }
2434 }
2435 }
2436
2437 //has permissions.
2438 if (!empty($hasPermissions)) {
2439 //need to check activity object specific.
fc9f05e0 2440 if (in_array($operation, [
e96f025a 2441 'view',
21dfd5f5 2442 'edit',
fc9f05e0 2443 ])
e96f025a 2444 ) {
6a488035
TO
2445 //do we have supper permission.
2446 if (in_array('access all cases and activities', $hasPermissions[$operation])) {
2447 $allowOperations[$operation] = $allow = TRUE;
2448 }
2449 else {
2450 //user has only access to my cases and activity.
2451 //here object specific permmions come in picture.
2452
2453 //edit - contact must be source or assignee
2454 //view - contact must be source/assignee/target
2455 $isTarget = $isAssignee = $isSource = FALSE;
44f817d4 2456 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
4322672b 2457 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
2458 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
2459 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
6a488035 2460
4e3d3cfc 2461 $target = new CRM_Activity_DAO_ActivityContact();
9e74e3ce 2462 $target->record_type_id = $targetID;
6a488035 2463 $target->activity_id = $activityId;
00bf7e59 2464 $target->contact_id = $contactId;
6a488035
TO
2465 if ($target->find(TRUE)) {
2466 $isTarget = TRUE;
2467 }
2468
4e3d3cfc 2469 $assignee = new CRM_Activity_DAO_ActivityContact();
6a488035 2470 $assignee->activity_id = $activityId;
9e74e3ce 2471 $assignee->record_type_id = $assigneeID;
00bf7e59 2472 $assignee->contact_id = $contactId;
6a488035
TO
2473 if ($assignee->find(TRUE)) {
2474 $isAssignee = TRUE;
2475 }
2476
4322672b 2477 $source = new CRM_Activity_DAO_ActivityContact();
2478 $source->activity_id = $activityId;
2479 $source->record_type_id = $sourceID;
2480 $source->contact_id = $contactId;
2481 if ($source->find(TRUE)) {
6a488035
TO
2482 $isSource = TRUE;
2483 }
2484
2485 if ($operation == 'edit') {
2486 if ($isAssignee || $isSource) {
2487 $allow = TRUE;
2488 }
2489 }
2490 if ($operation == 'view') {
2491 if ($isTarget || $isAssignee || $isSource) {
2492 $allow = TRUE;
2493 }
2494 }
2495 }
2496 }
2497 elseif (is_array($hasPermissions[$operation])) {
2498 $allowOperations[$operation] = $allow = TRUE;
2499 }
2500 }
2501 else {
2502 //contact do not have permission.
2503 $allowOperations[$operation] = FALSE;
2504 }
2505 }
2506 else {
2507 //use cache.
2508 //here contact might have supper/non permission.
2509 $allow = $allowOperations[$operation];
2510 }
2511 }
2512
2513 //do further only when operation is granted.
2514 if ($allow) {
b864360d 2515 $actTypeName = CRM_Core_PseudoConstant::getName('CRM_Activity_BAO_Activity', 'activity_type_id', $actTypeId);
6a488035
TO
2516
2517 //do not allow multiple copy / edit action.
fc9f05e0 2518 $singletonNames = [
e96f025a 2519 'Open Case',
2520 'Reassigned Case',
2521 'Merge Case',
2522 'Link Cases',
2523 'Assign Case Role',
2524 'Email',
21dfd5f5 2525 'Inbound Email',
fc9f05e0 2526 ];
6a488035
TO
2527
2528 //do not allow to delete these activities, CRM-4543
fc9f05e0 2529 $doNotDeleteNames = ['Open Case', 'Change Case Type', 'Change Case Status', 'Change Case Start Date'];
6a488035
TO
2530
2531 //allow edit operation.
fc9f05e0 2532 $allowEditNames = ['Open Case'];
6a488035 2533
426fe3c7 2534 if (CRM_Activity_BAO_Activity::checkEditInboundEmailsPermissions()) {
ee90a98c
CR
2535 $allowEditNames[] = 'Inbound Email';
2536 }
2537
6a488035 2538 // do not allow File on Case
fc9f05e0 2539 $doNotFileNames = [
e96f025a 2540 'Open Case',
2541 'Change Case Type',
2542 'Change Case Status',
2543 'Change Case Start Date',
2544 'Reassigned Case',
2545 'Merge Case',
2546 'Link Cases',
21dfd5f5 2547 'Assign Case Role',
fc9f05e0 2548 ];
6a488035
TO
2549
2550 if (in_array($actTypeName, $singletonNames)) {
2551 $allow = FALSE;
2552 if ($operation == 'File On Case') {
2553 $allow = (in_array($actTypeName, $doNotFileNames)) ? FALSE : TRUE;
2554 }
2555 if (in_array($operation, $actionOperations)) {
2556 $allow = TRUE;
2557 if ($operation == 'edit') {
2558 $allow = (in_array($actTypeName, $allowEditNames)) ? TRUE : FALSE;
2559 }
2560 elseif ($operation == 'delete') {
2561 $allow = (in_array($actTypeName, $doNotDeleteNames)) ? FALSE : TRUE;
2562 }
2563 }
2564 }
2565 if ($allow && ($operation == 'delete') &&
2566 in_array($actTypeName, $doNotDeleteNames)
2567 ) {
2568 $allow = FALSE;
2569 }
2570
2571 if ($allow && ($operation == 'File On Case') &&
2572 in_array($actTypeName, $doNotFileNames)
2573 ) {
2574 $allow = FALSE;
2575 }
2576
2577 //check settings file for masking actions
2578 //on the basis the activity types
2579 //hide Edit link if activity type is NOT editable
2580 //(special case activities).CRM-5871
2581 if ($allow && in_array($operation, $actionOperations)) {
fc9f05e0 2582 static $actionFilter = [];
6a488035
TO
2583 if (!array_key_exists($operation, $actionFilter)) {
2584 $xmlProcessor = new CRM_Case_XMLProcessor_Process();
2585 $actionFilter[$operation] = $xmlProcessor->get('Settings', 'ActivityTypes', FALSE, $operation);
2586 }
2587 if (array_key_exists($operation, $actionFilter[$operation]) &&
2588 in_array($actTypeId, $actionFilter[$operation][$operation])
2589 ) {
2590 $allow = FALSE;
2591 }
2592 }
2593 }
2594
2595 return $allow;
2596 }
2597
2598 /**
100fef9d 2599 * Since we drop 'access CiviCase', allow access
6a488035
TO
2600 * if user has 'access my cases and activities'
2601 * or 'access all cases and activities'
2602 */
00be9182 2603 public static function accessCiviCase() {
077dbf5e 2604 if (!self::enabled()) {
6a488035
TO
2605 return FALSE;
2606 }
2607
2608 if (CRM_Core_Permission::check('access my cases and activities') ||
2609 CRM_Core_Permission::check('access all cases and activities')
2610 ) {
2611 return TRUE;
2612 }
2613
2614 return FALSE;
2615 }
2616
2617 /**
d2e5d2ce 2618 * Verify user has permission to access a case.
077dbf5e
CW
2619 *
2620 * @param int $caseId
64bd5a0e
TO
2621 * @param bool $denyClosed
2622 * Set TRUE if one wants closed cases to be treated as inaccessible.
077dbf5e
CW
2623 *
2624 * @return bool
2625 */
00be9182 2626 public static function accessCase($caseId, $denyClosed = TRUE) {
077dbf5e
CW
2627 if (!$caseId || !self::enabled()) {
2628 return FALSE;
2629 }
2630
fc9f05e0 2631 $params = ['id' => $caseId, 'check_permissions' => TRUE];
3924e596 2632 if ($denyClosed && !CRM_Core_Permission::check('access all cases and activities')) {
fc9f05e0 2633 $params['status_id'] = ['!=' => 'Closed'];
546abeb1 2634 }
eeb45e43
CW
2635 try {
2636 return (bool) civicrm_api3('Case', 'getcount', $params);
2637 }
2638 catch (CiviCRM_API3_Exception $e) {
2639 // Lack of permissions will throw an exception
2640 return FALSE;
2641 }
077dbf5e
CW
2642 }
2643
6a488035 2644 /**
d2e5d2ce 2645 * Check whether activity is a case Activity.
6a488035 2646 *
64bd5a0e
TO
2647 * @param int $activityID
2648 * Activity id.
6a488035 2649 *
a130e045 2650 * @return bool
6a488035 2651 */
00be9182 2652 public static function isCaseActivity($activityID) {
6a488035
TO
2653 $isCaseActivity = FALSE;
2654 if ($activityID) {
fc9f05e0 2655 $params = [1 => [$activityID, 'Integer']];
6a488035
TO
2656 $query = "SELECT id FROM civicrm_case_activity WHERE activity_id = %1";
2657 if (CRM_Core_DAO::singleValueQuery($query, $params)) {
2658 $isCaseActivity = TRUE;
2659 }
2660 }
2661
2662 return $isCaseActivity;
2663 }
2664
2665 /**
d2e5d2ce 2666 * Get all the case type ids currently in use.
6a488035 2667 *
a6c01b45 2668 * @return array
6a488035 2669 */
00be9182 2670 public static function getUsedCaseType() {
6a488035
TO
2671 static $caseTypeIds;
2672
2673 if (!is_array($caseTypeIds)) {
2674 $query = "SELECT DISTINCT( civicrm_case.case_type_id ) FROM civicrm_case";
2675
2676 $dao = CRM_Core_DAO::executeQuery($query);
fc9f05e0 2677 $caseTypeIds = [];
6a488035
TO
2678 while ($dao->fetch()) {
2679 $typeId = explode(CRM_Core_DAO::VALUE_SEPARATOR,
2680 $dao->case_type_id
2681 );
2682 $caseTypeIds[] = $typeId[1];
2683 }
2684 }
2685
2686 return $caseTypeIds;
2687 }
2688
2689 /**
d2e5d2ce 2690 * Get all the case status ids currently in use.
6a488035 2691 *
a6c01b45 2692 * @return array
6a488035 2693 */
00be9182 2694 public static function getUsedCaseStatuses() {
6a488035
TO
2695 static $caseStatusIds;
2696
2697 if (!is_array($caseStatusIds)) {
2698 $query = "SELECT DISTINCT( civicrm_case.status_id ) FROM civicrm_case";
2699
2700 $dao = CRM_Core_DAO::executeQuery($query);
fc9f05e0 2701 $caseStatusIds = [];
6a488035
TO
2702 while ($dao->fetch()) {
2703 $caseStatusIds[] = $dao->status_id;
2704 }
2705 }
2706
2707 return $caseStatusIds;
2708 }
2709
2710 /**
d2e5d2ce 2711 * Get all the encounter medium ids currently in use.
72b3a70c 2712 *
6a488035
TO
2713 * @return array
2714 */
00be9182 2715 public static function getUsedEncounterMediums() {
6a488035
TO
2716 static $mediumIds;
2717
2718 if (!is_array($mediumIds)) {
2719 $query = "SELECT DISTINCT( civicrm_activity.medium_id ) FROM civicrm_activity";
2720
2721 $dao = CRM_Core_DAO::executeQuery($query);
fc9f05e0 2722 $mediumIds = [];
6a488035
TO
2723 while ($dao->fetch()) {
2724 $mediumIds[] = $dao->medium_id;
2725 }
2726 }
2727
2728 return $mediumIds;
2729 }
2730
2731 /**
100fef9d 2732 * Check case configuration.
6a488035 2733 *
100fef9d 2734 * @param int $contactId
77b97be7 2735 *
a6c01b45 2736 * @return array
6a488035 2737 */
00be9182 2738 public static function isCaseConfigured($contactId = NULL) {
fc9f05e0 2739 $configured = array_fill_keys(['configured', 'allowToAddNewCase', 'redirectToCaseAdmin'], FALSE);
6a488035
TO
2740
2741 //lets check for case configured.
2742 $allCasesCount = CRM_Case_BAO_Case::caseCount(NULL, FALSE);
2743 $configured['configured'] = ($allCasesCount) ? TRUE : FALSE;
2744 if (!$configured['configured']) {
2745 //do check for case type and case status.
0372ffa2 2746 $caseTypes = CRM_Case_PseudoConstant::caseType('title', FALSE);
6a488035
TO
2747 if (!empty($caseTypes)) {
2748 $configured['configured'] = TRUE;
2749 if (!$configured['configured']) {
2750 $caseStatuses = CRM_Case_PseudoConstant::caseStatus('label', FALSE);
2751 if (!empty($caseStatuses)) {
2752 $configured['configured'] = TRUE;
2753 }
2754 }
2755 }
2756 }
2757 if ($configured['configured']) {
2758 //do check for active case type and case status.
2759 $caseTypes = CRM_Case_PseudoConstant::caseType();
2760 if (!empty($caseTypes)) {
2761 $caseStatuses = CRM_Case_PseudoConstant::caseStatus();
2762 if (!empty($caseStatuses)) {
2763 $configured['allowToAddNewCase'] = TRUE;
2764 }
2765 }
2766
2767 //do we need to redirect user to case admin.
2768 if (!$configured['allowToAddNewCase'] && $contactId) {
2769 //check for current contact case count.
2770 $currentContatCasesCount = CRM_Case_BAO_Case::caseCount($contactId);
2771 //redirect user to case admin page.
2772 if (!$currentContatCasesCount) {
2773 $configured['redirectToCaseAdmin'] = TRUE;
2774 }
2775 }
2776 }
2777
2778 return $configured;
2779 }
2780
d6f468d3 2781 /**
d2e5d2ce 2782 * Used during case component enablement and during ugprade.
72b3a70c
CW
2783 *
2784 * @return bool
6a488035 2785 */
00be9182 2786 public static function createCaseViews() {
6a4257d4 2787 $errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
a19fc402
TO
2788 $dao = new CRM_Core_DAO();
2789
6a488035 2790 $sql = self::createCaseViewsQuery('upcoming');
6a488035
TO
2791 $dao->query($sql);
2792 if (PEAR::getStaticProperty('DB_DataObject', 'lastError')) {
6a488035
TO
2793 return FALSE;
2794 }
2795
2796 // Above error doesn't get caught?
2797 $doublecheck = $dao->singleValueQuery("SELECT count(id) FROM civicrm_view_case_activity_upcoming");
2798 if (is_null($doublecheck)) {
2799 return FALSE;
2800 }
2801
2802 $sql = self::createCaseViewsQuery('recent');
6a488035
TO
2803 $dao->query($sql);
2804 if (PEAR::getStaticProperty('DB_DataObject', 'lastError')) {
6a488035
TO
2805 return FALSE;
2806 }
2807
2808 // Above error doesn't get caught?
2809 $doublecheck = $dao->singleValueQuery("SELECT count(id) FROM civicrm_view_case_activity_recent");
2810 if (is_null($doublecheck)) {
2811 return FALSE;
2812 }
2813
2814 return TRUE;
2815 }
2816
d6f468d3 2817 /**
100fef9d 2818 * Helper function, also used by the upgrade in case of error
72b3a70c 2819 *
cde2037d
EM
2820 * @param string $section
2821 *
72b3a70c 2822 * @return string
6a488035 2823 */
00be9182 2824 public static function createCaseViewsQuery($section = 'upcoming') {
6a488035 2825 $sql = "";
0db2818f 2826 $scheduled_id = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Scheduled');
6a488035
TO
2827 switch ($section) {
2828 case 'upcoming':
2829 $sql = "CREATE OR REPLACE VIEW `civicrm_view_case_activity_upcoming`
2830 AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id
2831 FROM civicrm_case_activity ca
2832 INNER JOIN civicrm_activity a ON ca.activity_id=a.id
66d84429
AH
2833 WHERE a.id =
2834(SELECT b.id FROM civicrm_case_activity bca
2b95a25b 2835 INNER JOIN civicrm_activity b ON bca.activity_id=b.id
2836 WHERE b.activity_date_time <= DATE_ADD( NOW(), INTERVAL 14 DAY )
2837 AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id = $scheduled_id
2838 AND bca.case_id = ca.case_id ORDER BY b.activity_date_time ASC LIMIT 1)";
6a488035
TO
2839 break;
2840
2841 case 'recent':
2842 $sql = "CREATE OR REPLACE VIEW `civicrm_view_case_activity_recent`
2843 AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id
2844 FROM civicrm_case_activity ca
2845 INNER JOIN civicrm_activity a ON ca.activity_id=a.id
66d84429
AH
2846 WHERE a.id =
2847(SELECT b.id FROM civicrm_case_activity bca
2b95a25b 2848 INNER JOIN civicrm_activity b ON bca.activity_id=b.id
2849 WHERE b.activity_date_time >= DATE_SUB( NOW(), INTERVAL 14 DAY )
2850 AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id <> $scheduled_id
2851 AND bca.case_id = ca.case_id ORDER BY b.activity_date_time DESC LIMIT 1)";
6a488035
TO
2852 break;
2853 }
6a488035 2854 return $sql;
e96f025a 2855 }
2856
2857 /**
100fef9d 2858 * Add/copy relationships, when new client is added for a case
e96f025a 2859 *
64bd5a0e
TO
2860 * @param int $caseId
2861 * Case id.
2862 * @param int $contactId
2863 * Contact id / new client id.
e96f025a 2864 */
00be9182 2865 public static function addCaseRelationships($caseId, $contactId) {
44466d80
KJ
2866 // get the case role / relationships for the case
2867 $caseRelationships = new CRM_Contact_DAO_Relationship();
2868 $caseRelationships->case_id = $caseId;
2869 $caseRelationships->find();
fc9f05e0 2870 $relationshipTypes = [];
44466d80
KJ
2871
2872 // make sure we don't add duplicate relationships of same relationship type.
2873 while ($caseRelationships->fetch() && !in_array($caseRelationships->relationship_type_id, $relationshipTypes)) {
fc9f05e0 2874 $values = [];
44466d80
KJ
2875 CRM_Core_DAO::storeValues($caseRelationships, $values);
2876
2877 // add relationship for new client.
2878 $newRelationship = new CRM_Contact_DAO_Relationship();
2879 $newRelationship->copyValues($values);
2880 $newRelationship->id = NULL;
2881 $newRelationship->case_id = $caseId;
2882 $newRelationship->contact_id_a = $contactId;
2883 $newRelationship->end_date = CRM_Utils_Date::isoToMysql($caseRelationships->end_date);
2884 $newRelationship->start_date = CRM_Utils_Date::isoToMysql($caseRelationships->start_date);
2885
2886 // another check to avoid duplicate relationship, in cases where client is removed and re-added again.
2887 if (!$newRelationship->find(TRUE)) {
2888 $newRelationship->save();
2889 }
44466d80
KJ
2890
2891 // store relationship type of newly created relationship
2892 $relationshipTypes[] = $caseRelationships->relationship_type_id;
2893 }
6a488035 2894 }
14a679f1
KJ
2895
2896 /**
d2e5d2ce 2897 * Get the list of clients for a case.
14a679f1
KJ
2898 *
2899 * @param int $caseId
2900 *
a6c01b45
CW
2901 * @return array
2902 * associated array with client ids
14a679f1 2903 */
00be9182 2904 public static function getCaseClients($caseId) {
fc9f05e0 2905 $clients = [];
14a679f1
KJ
2906 $caseContact = new CRM_Case_DAO_CaseContact();
2907 $caseContact->case_id = $caseId;
78762324 2908 $caseContact->orderBy('id');
14a679f1
KJ
2909 $caseContact->find();
2910
e96f025a 2911 while ($caseContact->fetch()) {
14a679f1
KJ
2912 $clients[] = $caseContact->contact_id;
2913 }
2914
2915 return $clients;
2916 }
16c0ec8d 2917
3b1c37fe
CW
2918 /**
2919 * @param int $caseId
2920 * @param string $direction
2921 * @param int $cid
2922 * @param int $relTypeId
fc9f05e0 2923 *
3b1c37fe
CW
2924 * @throws \CRM_Core_Exception
2925 * @throws \CiviCRM_API3_Exception
2926 */
2927 public static function endCaseRole($caseId, $direction, $cid, $relTypeId) {
2928 // Validate inputs
2929 if ($direction !== 'a' && $direction !== 'b') {
2930 throw new CRM_Core_Exception('Invalid relationship direction');
2931 }
2932
2933 // This case might have multiple clients, so we lookup by relationship instead of by id to get them all
2934 $sql = "SELECT id FROM civicrm_relationship WHERE case_id = %1 AND contact_id_{$direction} = %2 AND relationship_type_id = %3";
fc9f05e0 2935 $dao = CRM_Core_DAO::executeQuery($sql, [
2936 1 => [$caseId, 'Positive'],
2937 2 => [$cid, 'Positive'],
2938 3 => [$relTypeId, 'Positive'],
2939 ]);
3b1c37fe 2940 while ($dao->fetch()) {
fc9f05e0 2941 civicrm_api3('relationship', 'create', [
3b1c37fe
CW
2942 'id' => $dao->id,
2943 'is_active' => 0,
2944 'end_date' => 'now',
fc9f05e0 2945 ]);
3b1c37fe
CW
2946 }
2947 }
2948
16c0ec8d
CW
2949 /**
2950 * Get options for a given case field.
16c0ec8d 2951 *
64bd5a0e
TO
2952 * @param string $fieldName
2953 * @param string $context
64bd5a0e 2954 * @param array $props
72b3a70c 2955 * Whatever is known about this dao object.
77b97be7 2956 *
a130e045 2957 * @return array|bool
fc9f05e0 2958 * @throws \CiviCRM_API3_Exception
2959 *
2960 * @see CRM_Core_DAO::buildOptionsContext
2961 * @see CRM_Core_DAO::buildOptions
2962 *
16c0ec8d 2963 */
fc9f05e0 2964 public static function buildOptions($fieldName, $context = NULL, $props = []) {
16c0ec8d 2965 $className = __CLASS__;
fc9f05e0 2966 $params = [];
16c0ec8d
CW
2967 switch ($fieldName) {
2968 // This field is not part of this object but the api supports it
2969 case 'medium_id':
2970 $className = 'CRM_Activity_BAO_Activity';
2971 break;
31c28ed5
CW
2972
2973 // Filter status id by case type id
2974 case 'status_id':
2975 if (!empty($props['case_type_id'])) {
2976 $idField = is_numeric($props['case_type_id']) ? 'id' : 'name';
fc9f05e0 2977 $caseType = civicrm_api3('CaseType', 'getsingle', [$idField => $props['case_type_id'], 'return' => 'definition']);
31c28ed5
CW
2978 if (!empty($caseType['definition']['statuses'])) {
2979 $params['condition'] = 'v.name IN ("' . implode('","', $caseType['definition']['statuses']) . '")';
2980 }
2981 }
2982 break;
16c0ec8d
CW
2983 }
2984 return CRM_Core_PseudoConstant::get($className, $fieldName, $params, $context);
2985 }
96025800 2986
174a1918
CW
2987 /**
2988 * @inheritDoc
2989 */
20e41014 2990 public function addSelectWhereClause() {
0b80f0b4
CW
2991 // We always return an array with these keys, even if they are empty,
2992 // because this tells the query builder that we have considered these fields for acls
fc9f05e0 2993 $clauses = [
2994 'id' => [],
ff9340a4 2995 // Only case admins can view deleted cases
fc9f05e0 2996 'is_deleted' => CRM_Core_Permission::check('administer CiviCase') ? [] : ["= 0"],
2997 ];
174a1918 2998 // Ensure the user has permission to view the case client
d1d3c04a 2999 $contactClause = CRM_Utils_SQL::mergeSubquery('Contact');
ff9340a4
CW
3000 if ($contactClause) {
3001 $contactClause = implode(' AND contact_id ', $contactClause);
3002 $clauses['id'][] = "IN (SELECT case_id FROM civicrm_case_contact WHERE contact_id $contactClause)";
174a1918 3003 }
0b80f0b4 3004 // The api gatekeeper ensures the user has at least "access my cases and activities"
174a1918
CW
3005 // so if they do not have permission to see all cases we'll assume they can only access their own
3006 if (!CRM_Core_Permission::check('access all cases and activities')) {
3007 $user = (int) CRM_Core_Session::getLoggedInContactID();
ff9340a4 3008 $clauses['id'][] = "IN (
174a1918 3009 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 3010 (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
3011 )
3012 )";
3013 }
2b240c0c 3014 CRM_Utils_Hook::selectWhereClause($this, $clauses);
ff9340a4 3015 return $clauses;
174a1918
CW
3016 }
3017
8aeeea00 3018 /**
3cf1fae9 3019 * CRM-20308: Method to get the contact id to use as from contact for email copy
8aeeea00
EH
3020 * 1. Activity Added by Contact's email address
3021 * 2. System Default From Address
3022 * 3. Default Organization Contact email address
3023 * 4. Logged in user
3024 *
3cf1fae9 3025 * @param int $activityID
3026 *
8aeeea00 3027 * @return mixed $emailFromContactId
fc9f05e0 3028 *
3029 * @throws \CiviCRM_API3_Exception
8aeeea00
EH
3030 * @see https://issues.civicrm.org/jira/browse/CRM-20308
3031 */
3cf1fae9 3032 public static function getReceiptFrom($activityID) {
3033 $name = $address = NULL;
3034
4c981f37
MW
3035 if (!empty($activityID) && (Civi::settings()->get('allow_mail_from_logged_in_contact'))) {
3036 // This breaks SPF/DMARC if email is sent from an email address that the server is not authorised to send from.
3037 // so we can disable this behaviour with the "allow_mail_from_logged_in_contact" setting.
3cf1fae9 3038 // There is always a 'Added by' contact for a activity,
3039 // so we can safely use ActivityContact.Getvalue API
fc9f05e0 3040 $sourceContactId = civicrm_api3('ActivityContact', 'getvalue', [
3cf1fae9 3041 'activity_id' => $activityID,
3042 'record_type_id' => 'Activity Source',
3043 'return' => 'contact_id',
fc9f05e0 3044 ]);
3cf1fae9 3045 list($name, $address) = CRM_Contact_BAO_Contact_Location::getEmailDetails($sourceContactId);
8aeeea00 3046 }
3cf1fae9 3047
3048 // If 'From' email address not found for Source Activity Contact then
3049 // fetch the email from domain or logged in user.
3050 if (empty($address)) {
3051 list($name, $address) = CRM_Core_BAO_Domain::getDefaultReceiptFrom();
8aeeea00 3052 }
3cf1fae9 3053
8aeeea00
EH
3054 return "$name <$address>";
3055 }
3056
1d6f94ab
CW
3057 /**
3058 * @return array
3059 */
3060 public static function getEntityRefFilters() {
3061 $filters = [
3062 [
3063 'key' => 'case_id.case_type_id',
3064 'value' => ts('Case Type'),
3065 'entity' => 'Case',
3066 ],
3067 [
3068 'key' => 'case_id.status_id',
3069 'value' => ts('Case Status'),
3070 'entity' => 'Case',
3071 ],
3072 ];
3073 foreach (CRM_Contact_BAO_Contact::getEntityRefFilters() as $filter) {
2229cf4f 3074 $filter += ['entity' => 'Contact'];
1d6f94ab
CW
3075 $filter['key'] = 'contact_id.' . $filter['key'];
3076 $filters[] = $filter;
3077 }
3078 return $filters;
3079 }
3080
41cf58d3
AF
3081 /**
3082 * Fetch Case Role direction from Case Type
3083 */
3084 public static function getCaseRoleDirection($caseId, $roleTypeId = NULL) {
3085 try {
fc9f05e0 3086 $case = civicrm_api3('Case', 'getsingle', ['id' => $caseId]);
41cf58d3
AF
3087 }
3088 catch (CiviCRM_API3_Exception $e) {
3089 // Lack of permissions will throw an exception
3090 return 0;
3091 }
3092 if (!empty($case['case_type_id'])) {
3093 try {
fc9f05e0 3094 $caseType = civicrm_api3('CaseType', 'getsingle', ['id' => $case['case_type_id'], 'return' => ['definition']]);
41cf58d3
AF
3095 }
3096 catch (CiviCRM_API3_Exception $e) {
3097 // Lack of permissions will throw an exception
3098 return 'no case type found';
3099 }
3100 if (!empty($caseType['definition']['caseRoles'])) {
fc9f05e0 3101 $caseRoles = [];
41cf58d3
AF
3102 foreach ($caseType['definition']['caseRoles'] as $key => $roleDetails) {
3103 // Check if its an a_b label
3104 try {
fc9f05e0 3105 $relType = civicrm_api3('RelationshipType', 'getsingle', ['label_a_b' => $roleDetails['name']]);
41cf58d3
AF
3106 }
3107 catch (CiviCRM_API3_Exception $e) {
3108 }
3109 if (!empty($relType['id'])) {
3110 $roleDetails['id'] = $relType['id'];
d0a94888 3111 $roleDetails['direction'] = 'b_a';
41cf58d3
AF
3112 }
3113 // Check if its a b_a label
3114 try {
fc9f05e0 3115 $relTypeBa = civicrm_api3('RelationshipType', 'getsingle', ['label_b_a' => $roleDetails['name']]);
41cf58d3
AF
3116 }
3117 catch (CiviCRM_API3_Exception $e) {
3118 }
3119 if (!empty($relTypeBa['id'])) {
3120 if (!empty($roleDetails['direction'])) {
3121 $roleDetails['direction'] = 'bidrectional';
3122 }
3123 else {
3124 $roleDetails['id'] = $relTypeBa['id'];
d0a94888 3125 $roleDetails['direction'] = 'a_b';
41cf58d3
AF
3126 }
3127 }
3128 $caseRoles[$roleDetails['id']] = $roleDetails;
3129 }
3130 }
3131 return $caseRoles;
3132 }
3133 }
3134
6a488035 3135}