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