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