Merge pull request #15825 from seamuslee001/dev_core_183_logging
[civicrm-core.git] / CRM / Case / Selector / Search.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class is used to retrieve and display a range of contacts that match the given criteria.
20 */
21 class CRM_Case_Selector_Search extends CRM_Core_Selector_Base {
22
23 /**
24 * This defines two actions- View and Edit.
25 *
26 * @var array
27 */
28 public static $_links = NULL;
29
30 /**
31 * The action links that we need to display for the browse screen.
32 *
33 * @var array
34 */
35 private static $_actionLinks;
36
37 /**
38 * We use desc to remind us what that column is, name is used in the tpl
39 *
40 * @var array
41 */
42 public static $_columnHeaders;
43
44 /**
45 * Properties of contact we're interested in displaying
46 * @var array
47 */
48 public static $_properties = [
49 'contact_id',
50 'contact_type',
51 'sort_name',
52 'display_name',
53 'case_id',
54 'case_subject',
55 'case_status_id',
56 'case_status',
57 'case_type_id',
58 'case_type',
59 'case_role',
60 'phone',
61 ];
62
63 /**
64 * Are we restricting ourselves to a single contact
65 *
66 * @var bool
67 */
68 protected $_single = FALSE;
69
70 /**
71 * Are we restricting ourselves to a single contact
72 *
73 * @var bool
74 */
75 protected $_limit = NULL;
76
77 /**
78 * What context are we being invoked from
79 *
80 * @var string
81 */
82 protected $_context = NULL;
83
84 /**
85 * QueryParams is the array returned by exportValues called on
86 * the HTML_QuickForm_Controller for that page.
87 *
88 * @var array
89 */
90 public $_queryParams;
91
92 /**
93 * Represent the type of selector
94 *
95 * @var int
96 */
97 protected $_action;
98
99 /**
100 * The additional clause that we restrict the search with
101 *
102 * @var string
103 */
104 protected $_additionalClause = NULL;
105
106 /**
107 * The query object
108 *
109 * @var string
110 */
111 protected $_query;
112
113 /**
114 * Class constructor.
115 *
116 * @param array $queryParams
117 * Array of parameters for query.
118 * @param \const|int $action - action of search basic or advanced.
119 * @param string $additionalClause
120 * If the caller wants to further restrict the search (used in participations).
121 * @param bool $single
122 * Are we dealing only with one contact?.
123 * @param int $limit
124 * How many signers do we want returned.
125 *
126 * @param string $context
127 *
128 * @return \CRM_Case_Selector_Search
129 */
130 public function __construct(
131 &$queryParams,
132 $action = CRM_Core_Action::NONE,
133 $additionalClause = NULL,
134 $single = FALSE,
135 $limit = NULL,
136 $context = 'search'
137 ) {
138 // submitted form values
139 $this->_queryParams = &$queryParams;
140
141 $this->_single = $single;
142 $this->_limit = $limit;
143 $this->_context = $context;
144
145 $this->_additionalClause = $additionalClause;
146
147 // type of selector
148 $this->_action = $action;
149
150 $this->_query = new CRM_Contact_BAO_Query($this->_queryParams,
151 CRM_Case_BAO_Query::defaultReturnProperties(CRM_Contact_BAO_Query::MODE_CASE,
152 FALSE
153 ),
154 NULL, FALSE, FALSE,
155 CRM_Contact_BAO_Query::MODE_CASE
156 );
157
158 $this->_query->_distinctComponentClause = " civicrm_case.id ";
159 $this->_query->_groupByComponentClause = " GROUP BY civicrm_case.id ";
160 }
161
162 /**
163 * This method returns the links that are given for each search row.
164 * currently the links added for each row are
165 *
166 * - View
167 * - Edit
168 *
169 * @param bool $isDeleted
170 * @param null $key
171 *
172 * @return array
173 */
174 public static function &links($isDeleted = FALSE, $key = NULL) {
175 $extraParams = ($key) ? "&key={$key}" : NULL;
176
177 if ($isDeleted) {
178 self::$_links = [
179 CRM_Core_Action::RENEW => [
180 'name' => ts('Restore'),
181 'url' => 'civicrm/contact/view/case',
182 'qs' => 'reset=1&action=renew&id=%%id%%&cid=%%cid%%&context=%%cxt%%' . $extraParams,
183 'ref' => 'restore-case',
184 'title' => ts('Restore Case'),
185 ],
186 ];
187 }
188 else {
189 self::$_links = [
190 CRM_Core_Action::VIEW => [
191 'name' => ts('Manage'),
192 'url' => 'civicrm/contact/view/case',
193 'qs' => 'reset=1&id=%%id%%&cid=%%cid%%&action=view&context=%%cxt%%&selectedChild=case' . $extraParams,
194 'ref' => 'manage-case',
195 'class' => 'no-popup',
196 'title' => ts('Manage Case'),
197 ],
198 CRM_Core_Action::DELETE => [
199 'name' => ts('Delete'),
200 'url' => 'civicrm/contact/view/case',
201 'qs' => 'reset=1&action=delete&id=%%id%%&cid=%%cid%%&context=%%cxt%%' . $extraParams,
202 'ref' => 'delete-case',
203 'title' => ts('Delete Case'),
204 ],
205 CRM_Core_Action::UPDATE => [
206 'name' => ts('Assign to Another Client'),
207 'url' => 'civicrm/contact/view/case/editClient',
208 'qs' => 'reset=1&action=update&id=%%id%%&cid=%%cid%%&context=%%cxt%%' . $extraParams,
209 'ref' => 'reassign',
210 'class' => 'medium-popup',
211 'title' => ts('Assign to Another Client'),
212 ],
213 ];
214 }
215
216 $actionLinks = [];
217 foreach (self::$_links as $key => $value) {
218 $actionLinks['primaryActions'][$key] = $value;
219 }
220
221 return $actionLinks;
222 }
223
224 /**
225 * Getter for array of the parameters required for creating pager.
226 *
227 * @param $action
228 * @param array $params
229 */
230 public function getPagerParams($action, &$params) {
231 $params['status'] = ts('Case') . ' %%StatusMessage%%';
232 $params['csvString'] = NULL;
233 if ($this->_limit) {
234 $params['rowCount'] = $this->_limit;
235 }
236 else {
237 $params['rowCount'] = CRM_Utils_Pager::ROWCOUNT;
238 }
239
240 $params['buttonTop'] = 'PagerTopButton';
241 $params['buttonBottom'] = 'PagerBottomButton';
242 }
243
244 /**
245 * Returns total number of rows for the query.
246 *
247 * @param
248 *
249 * @return int
250 * Total number of rows
251 */
252 public function getTotalCount($action) {
253 return $this->_query->searchQuery(0, 0, NULL,
254 TRUE, FALSE,
255 FALSE, FALSE,
256 FALSE,
257 $this->_additionalClause
258 );
259 }
260
261 /**
262 * Returns all the rows in the given offset and rowCount.
263 *
264 * @param string $action
265 * The action being performed.
266 * @param int $offset
267 * The row number to start from.
268 * @param int $rowCount
269 * The number of rows to return.
270 * @param string $sort
271 * The sql string that describes the sort order.
272 * @param string $output
273 * What should the result set include (web/email/csv).
274 *
275 * @return int
276 * the total number of rows for this action
277 */
278 public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) {
279 $result = $this->_query->searchQuery($offset, $rowCount, $sort,
280 FALSE, FALSE,
281 FALSE, FALSE,
282 FALSE,
283 $this->_additionalClause
284 );
285 // process the result of the query
286 $rows = [];
287
288 //CRM-4418 check for view, edit, delete
289 $permissions = [CRM_Core_Permission::VIEW];
290 if (CRM_Core_Permission::check('access all cases and activities')
291 || CRM_Core_Permission::check('access my cases and activities')
292 ) {
293 $permissions[] = CRM_Core_Permission::EDIT;
294 }
295 if (CRM_Core_Permission::check('delete in CiviCase')) {
296 $permissions[] = CRM_Core_Permission::DELETE;
297 }
298 $mask = CRM_Core_Action::mask($permissions);
299
300 $caseStatus = CRM_Core_OptionGroup::values('case_status', FALSE, FALSE, FALSE, " AND v.name = 'Urgent' ");
301
302 $scheduledInfo = [];
303
304 while ($result->fetch()) {
305 $row = [];
306 // the columns we are interested in
307 foreach (self::$_properties as $property) {
308 if (isset($result->$property)) {
309 $row[$property] = $result->$property;
310 }
311 }
312
313 $isDeleted = FALSE;
314 if ($result->case_deleted) {
315 $isDeleted = TRUE;
316 $row['case_status_id'] = empty($row['case_status_id']) ? "" : $row['case_status_id'] . '<br />' . ts('(deleted)');
317 }
318
319 $scheduledInfo['case_id'][] = $result->case_id;
320 $scheduledInfo['contact_id'][] = $result->contact_id;
321 $scheduledInfo['case_deleted'] = $result->case_deleted;
322 $row['checkbox'] = CRM_Core_Form::CB_PREFIX . $result->case_id;
323
324 $links = self::links($isDeleted, $this->_key);
325 $row['action'] = CRM_Core_Action::formLink($links['primaryActions'],
326 $mask, [
327 'id' => $result->case_id,
328 'cid' => $result->contact_id,
329 'cxt' => $this->_context,
330 ],
331 ts('more'),
332 FALSE,
333 'case.selector.actions',
334 'Case',
335 $result->case_id
336 );
337
338 $row['contact_type'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type
339 );
340
341 //adding case manager to case selector.CRM-4510.
342 $caseType = CRM_Case_BAO_Case::getCaseType($result->case_id, 'name');
343 $row['casemanager'] = CRM_Case_BAO_Case::getCaseManagerContact($caseType, $result->case_id);
344
345 if (isset($result->case_status_id) &&
346 array_key_exists($result->case_status_id, $caseStatus)
347 ) {
348 $row['class'] = "status-urgent";
349 }
350 else {
351 $row['class'] = "status-normal";
352 }
353
354 $rows[$result->case_id] = $row;
355 }
356
357 //retrive the scheduled & recent Activity type and date for selector
358 if (!empty($scheduledInfo)) {
359 $schdeduledActivity = CRM_Case_BAO_Case::getNextScheduledActivity($scheduledInfo, 'upcoming');
360 foreach ($schdeduledActivity as $key => $value) {
361 $rows[$key]['case_scheduled_activity_date'] = $value['date'];
362 $rows[$key]['case_scheduled_activity_type'] = $value['type'];
363 }
364 $recentActivity = CRM_Case_BAO_Case::getNextScheduledActivity($scheduledInfo, 'recent');
365 foreach ($recentActivity as $key => $value) {
366 $rows[$key]['case_recent_activity_date'] = $value['date'];
367 $rows[$key]['case_recent_activity_type'] = $value['type'];
368 }
369 }
370 return $rows;
371 }
372
373 /**
374 * @inheritDoc
375 */
376 public function getQILL() {
377 return $this->_query->qill();
378 }
379
380 /**
381 * Returns the column headers as an array of tuples:
382 * (name, sortName (key to the sort array))
383 *
384 * @param string $action
385 * The action being performed.
386 * @param string $output
387 * What should the result set include (web/email/csv).
388 *
389 * @return array
390 * the column headers that need to be displayed
391 */
392 public function &getColumnHeaders($action = NULL, $output = NULL) {
393 if (!isset(self::$_columnHeaders)) {
394 self::$_columnHeaders = [
395 [
396 'name' => ts('Subject'),
397 'direction' => CRM_Utils_Sort::DONTCARE,
398 ],
399 [
400 'name' => ts('Status'),
401 'sort' => 'case_status',
402 'direction' => CRM_Utils_Sort::DONTCARE,
403 ],
404 [
405 'name' => ts('Case Type'),
406 'sort' => 'case_type',
407 'direction' => CRM_Utils_Sort::DONTCARE,
408 ],
409 [
410 'name' => ts('My Role'),
411 'sort' => 'case_role',
412 'direction' => CRM_Utils_Sort::DONTCARE,
413 ],
414 [
415 'name' => ts('Case Manager'),
416 'direction' => CRM_Utils_Sort::DONTCARE,
417 ],
418 [
419 'name' => ts('Most Recent'),
420 'sort' => 'case_recent_activity_date',
421 'direction' => CRM_Utils_Sort::DONTCARE,
422 ],
423 [
424 'name' => ts('Next Sched.'),
425 'sort' => 'case_scheduled_activity_date',
426 'direction' => CRM_Utils_Sort::DONTCARE,
427 ],
428 ['name' => ts('Actions')],
429 ];
430
431 if (!$this->_single) {
432 $pre = [
433 [
434 'name' => ts('Client'),
435 'sort' => 'sort_name',
436 'direction' => CRM_Utils_Sort::ASCENDING,
437 ],
438 ];
439
440 self::$_columnHeaders = array_merge($pre, self::$_columnHeaders);
441 }
442 }
443 return self::$_columnHeaders;
444 }
445
446 /**
447 * @return mixed
448 */
449 public function alphabetQuery() {
450 return $this->_query->alphabetQuery();
451 }
452
453 /**
454 * @return string
455 */
456 public function &getQuery() {
457 return $this->_query;
458 }
459
460 /**
461 * Name of export file.
462 *
463 * @param string $output
464 * Type of output.
465 *
466 * @return string
467 * name of the file
468 */
469 public function getExportFileName($output = 'csv') {
470 return ts('Case Search');
471 }
472
473 /**
474 * Add the set of "actionLinks" to the case activity
475 *
476 * @param int $caseID
477 * @param int $contactID
478 * @param int $userID
479 * @param string $context
480 * @param \CRM_Activity_BAO_Activity $dao
481 * @param bool $allowView
482 *
483 * @return string $linksMarkup
484 */
485 public static function addCaseActivityLinks($caseID, $contactID, $userID, $context, $dao, $allowView = TRUE) {
486 $caseDeleted = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseID, 'is_deleted');
487 $actionLinks = self::actionLinks();
488 // Check logged in user for permission.
489 if (CRM_Case_BAO_Case::checkPermission($dao->id, 'view', $dao->activity_type_id, $userID)) {
490 $permissions[] = CRM_Core_Permission::VIEW;
491 }
492 if (!$allowView) {
493 unset($actionLinks[CRM_Core_Action::VIEW]);
494 }
495 if (!$dao->deleted) {
496 // Activity is not deleted, allow user to edit/delete if they have permission
497 // hide Edit link if:
498 // 1. User does not have edit permission.
499 // 2. Activity type is NOT editable (special case activities).CRM-5871
500 if (CRM_Case_BAO_Case::checkPermission($dao->id, 'edit', $dao->activity_type_id, $userID)) {
501 $permissions[] = CRM_Core_Permission::EDIT;
502 }
503 if (in_array($dao->activity_type_id, CRM_Activity_BAO_Activity::getViewOnlyActivityTypeIDs())) {
504 unset($actionLinks[CRM_Core_Action::UPDATE]);
505 }
506 if (CRM_Case_BAO_Case::checkPermission($dao->id, 'delete', $dao->activity_type_id, $userID)) {
507 $permissions[] = CRM_Core_Permission::DELETE;
508 }
509 unset($actionLinks[CRM_Core_Action::RENEW]);
510 }
511 $extraMask = 0;
512 if ($dao->deleted && !$caseDeleted
513 && (CRM_Case_BAO_Case::checkPermission($dao->id, 'delete', $dao->activity_type_id, $userID))) {
514 // Case is not deleted but activity is.
515 // Allow user to restore activity if they have delete permissions
516 unset($actionLinks[CRM_Core_Action::DELETE]);
517 $extraMask = CRM_Core_Action::RENEW;
518 }
519 if (!CRM_Case_BAO_Case::checkPermission($dao->id, 'Move To Case', $dao->activity_type_id)) {
520 unset($actionLinks[CRM_Core_Action::DETACH]);
521 }
522 if (!CRM_Case_BAO_Case::checkPermission($dao->id, 'Copy To Case', $dao->activity_type_id)) {
523 unset($actionLinks[CRM_Core_Action::COPY]);
524 }
525 $actionMask = CRM_Core_Action::mask($permissions) | $extraMask;
526 $values = [
527 'aid' => $dao->id,
528 'cid' => $contactID,
529 'cxt' => empty($context) ? '' : "&context={$context}",
530 'caseid' => $caseID,
531 ];
532 $linksMarkup = CRM_Core_Action::formLink($actionLinks,
533 $actionMask,
534 $values,
535 ts('more'),
536 FALSE,
537 'case.tab.row',
538 'Activity',
539 $dao->id
540 );
541 // if there are file attachments we will return how many and, if only one, add a link to it
542 if (!empty($dao->attachment_ids)) {
543 $linksMarkup .= implode(' ', CRM_Core_BAO_File::paperIconAttachment('civicrm_activity', $dao->id));
544 }
545 return $linksMarkup;
546 }
547
548 /**
549 * @param int $caseID
550 * @param int $contactID
551 * @param int $userID
552 * @param string $context
553 * @param int $activityTypeID
554 * @param int $activityDeleted
555 * @param int $activityID
556 * @param bool $allowView
557 *
558 * @return array|null
559 */
560 public static function permissionedActionLinks($caseID, $contactID, $userID, $context, $activityTypeID, $activityDeleted, $activityID, $allowView = TRUE) {
561 $caseDeleted = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseID, 'is_deleted');
562 $values = [
563 'aid' => $activityID,
564 'cid' => $contactID,
565 'cxt' => empty($context) ? '' : "&context={$context}",
566 'caseid' => $caseID,
567 ];
568 $actionLinks = self::actionLinks();
569
570 // Check logged in user for permission.
571 if (CRM_Case_BAO_Case::checkPermission($activityID, 'view', $activityTypeID, $userID)) {
572 $permissions[] = CRM_Core_Permission::VIEW;
573 }
574 if (!$allowView) {
575 unset($actionLinks[CRM_Core_Action::VIEW]);
576 }
577 if (!$activityDeleted) {
578 // Activity is not deleted, allow user to edit/delete if they have permission
579
580 // hide Edit link if:
581 // 1. User does not have edit permission.
582 // 2. Activity type is NOT editable (special case activities).CRM-5871
583 if (CRM_Case_BAO_Case::checkPermission($activityID, 'edit', $activityTypeID, $userID)) {
584 $permissions[] = CRM_Core_Permission::EDIT;
585 }
586 if (in_array($activityTypeID, CRM_Activity_BAO_Activity::getViewOnlyActivityTypeIDs())) {
587 unset($actionLinks[CRM_Core_Action::UPDATE]);
588 }
589 if (CRM_Case_BAO_Case::checkPermission($activityID, 'delete', $activityTypeID, $userID)) {
590 $permissions[] = CRM_Core_Permission::DELETE;
591 }
592 unset($actionLinks[CRM_Core_Action::RENEW]);
593 }
594 $extraMask = 0;
595 if ($activityDeleted && !$caseDeleted
596 && (CRM_Case_BAO_Case::checkPermission($activityID, 'delete', $activityTypeID, $userID))) {
597 // Case is not deleted but activity is.
598 // Allow user to restore activity if they have delete permissions
599 unset($actionLinks[CRM_Core_Action::DELETE]);
600 $extraMask = CRM_Core_Action::RENEW;
601 }
602 if (!CRM_Case_BAO_Case::checkPermission($activityID, 'Move To Case', $activityTypeID)) {
603 unset($actionLinks[CRM_Core_Action::DETACH]);
604 }
605 if (!CRM_Case_BAO_Case::checkPermission($activityID, 'Copy To Case', $activityTypeID)) {
606 unset($actionLinks[CRM_Core_Action::COPY]);
607 }
608
609 $actionMask = CRM_Core_Action::mask($permissions) | $extraMask;
610 return CRM_Core_Action::filterLinks($actionLinks, $actionMask, $values, 'case.activity', 'Activity', $activityID);
611 }
612
613 /**
614 * Get the action links for this page.
615 *
616 * @return array
617 */
618 public static function actionLinks() {
619 // check if variable _actionsLinks is populated
620 if (!isset(self::$_actionLinks)) {
621 self::$_actionLinks = [
622 CRM_Core_Action::VIEW => [
623 'name' => ts('View'),
624 'url' => 'civicrm/case/activity/view',
625 'qs' => 'reset=1&cid=%%cid%%&caseid=%%caseid%%&aid=%%aid%%',
626 'title' => ts('View'),
627 ],
628 CRM_Core_Action::UPDATE => [
629 'name' => ts('Edit'),
630 'url' => 'civicrm/case/activity',
631 'qs' => 'reset=1&cid=%%cid%%&caseid=%%caseid%%&id=%%aid%%&action=update%%cxt%%',
632 'title' => ts('Edit'),
633 'icon' => 'fa-pencil',
634 ],
635 CRM_Core_Action::DELETE => [
636 'name' => ts('Delete'),
637 'url' => 'civicrm/case/activity',
638 'qs' => 'reset=1&cid=%%cid%%&caseid=%%caseid%%&id=%%aid%%&action=delete%%cxt%%',
639 'title' => ts('Delete'),
640 'icon' => 'fa-trash',
641 ],
642 CRM_Core_Action::RENEW => [
643 'name' => ts('Restore'),
644 'url' => 'civicrm/case/activity',
645 'qs' => 'reset=1&cid=%%cid%%&caseid=%%caseid%%&id=%%aid%%&action=renew%%cxt%%',
646 'title' => ts('Restore'),
647 'icon' => 'fa-undo',
648 ],
649 CRM_Core_Action::DETACH => [
650 'name' => ts('Move To Case'),
651 'ref' => 'move_to_case_action',
652 'title' => ts('Move To Case'),
653 'extra' => 'onclick = "Javascript:fileOnCase( \'move\', %%aid%%, %%caseid%%, this ); return false;"',
654 'icon' => 'fa-clipboard',
655 ],
656 CRM_Core_Action::COPY => [
657 'name' => ts('Copy To Case'),
658 'ref' => 'copy_to_case_action',
659 'title' => ts('Copy To Case'),
660 'extra' => 'onclick = "Javascript:fileOnCase( \'copy\', %%aid%%, %%caseid%%, this ); return false;"',
661 'icon' => 'fa-files-o',
662 ],
663 ];
664 }
665 return self::$_actionLinks;
666 }
667
668 }