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