Merge pull request #22982 from braders/filter_sanitize_string-deprecation
[civicrm-core.git] / CRM / Case / XMLProcessor / Process.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 class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
18 protected $defaultAssigneeOptionsValues = [];
19
20 /**
21 * Run.
22 *
23 * @param string $caseType
24 * @param array $params
25 *
26 * @throws CRM_Core_Exception
27 */
28 public function run($caseType, &$params) {
29 $xml = $this->retrieve($caseType);
30
31 if ($xml === FALSE) {
32 $docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
33 throw new CRM_Core_Exception(ts("Configuration file could not be retrieved for case type = '%1' %2.",
34 [1 => $caseType, 2 => $docLink]
35 ));
36 }
37
38 $xmlProcessorProcess = new CRM_Case_XMLProcessor_Process();
39 $this->_isMultiClient = $xmlProcessorProcess->getAllowMultipleCaseClients();
40
41 $this->process($xml, $params);
42 }
43
44 /**
45 * @param $caseType
46 * @param $fieldSet
47 * @param bool $isLabel
48 * @param bool $maskAction
49 *
50 * @return array|bool|mixed
51 * @throws Exception
52 */
53 public function get($caseType, $fieldSet, $isLabel = FALSE, $maskAction = FALSE) {
54 $xml = $this->retrieve($caseType);
55 if ($xml === FALSE) {
56 $docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
57 throw new CRM_Core_Exception(ts("Unable to load configuration file for the referenced case type: '%1' %2.",
58 [1 => $caseType, 2 => $docLink]
59 ));
60 }
61
62 switch ($fieldSet) {
63 case 'CaseRoles':
64 return $this->caseRoles($xml->CaseRoles);
65
66 case 'ActivitySets':
67 return $this->activitySets($xml->ActivitySets);
68
69 case 'ActivityTypes':
70 return $this->activityTypes($xml->ActivityTypes, FALSE, $isLabel, $maskAction);
71 }
72 }
73
74 /**
75 * @param $xml
76 * @param array $params
77 *
78 * @throws Exception
79 */
80 public function process($xml, &$params) {
81 $standardTimeline = $params['standardTimeline'] ?? NULL;
82 $activitySetName = $params['activitySetName'] ?? NULL;
83
84 if ('Open Case' == CRM_Utils_Array::value('activityTypeName', $params)) {
85 // create relationships for the ones that are required
86 foreach ($xml->CaseRoles as $caseRoleXML) {
87 foreach ($caseRoleXML->RelationshipType as $relationshipTypeXML) {
88 // simplexml treats node values differently than you'd expect,
89 // e.g. as an array
90 // Just using `if ($relationshipTypeXML->creator)` ends up always
91 // being true, so you have to cast to int or somehow force evaluation
92 // of the actual value. And casting to (bool) seems to behave
93 // differently on these objects than casting to (int).
94 if (!empty($relationshipTypeXML->creator)) {
95 if (!$this->createRelationships($relationshipTypeXML,
96 $params
97 )
98 ) {
99 throw new CRM_Core_Exception('Unable to create case relationships');
100 }
101 }
102 }
103 }
104 }
105
106 if ('Change Case Start Date' == CRM_Utils_Array::value('activityTypeName', $params)) {
107 // delete all existing activities which are non-empty
108 $this->deleteEmptyActivity($params);
109 }
110
111 foreach ($xml->ActivitySets as $activitySetsXML) {
112 foreach ($activitySetsXML->ActivitySet as $activitySetXML) {
113 if ($standardTimeline) {
114 if (!empty($activitySetXML->timeline)) {
115 return $this->processStandardTimeline($activitySetXML, $params);
116 }
117 }
118 elseif ($activitySetName) {
119 $name = (string) $activitySetXML->name;
120 if ($name == $activitySetName) {
121 return $this->processActivitySet($activitySetXML, $params);
122 }
123 }
124 }
125 }
126 }
127
128 /**
129 * @param $activitySetXML
130 * @param array $params
131 */
132 public function processStandardTimeline($activitySetXML, &$params) {
133 if ('Change Case Type' == CRM_Utils_Array::value('activityTypeName', $params)
134 && CRM_Utils_Array::value('resetTimeline', $params, TRUE)
135 ) {
136 // delete all existing activities which are non-empty
137 $this->deleteEmptyActivity($params);
138 }
139
140 foreach ($activitySetXML->ActivityTypes as $activityTypesXML) {
141 foreach ($activityTypesXML as $activityTypeXML) {
142 $this->createActivity($activityTypeXML, $params);
143 }
144 }
145 }
146
147 /**
148 * @param $activitySetXML
149 * @param array $params
150 */
151 public function processActivitySet($activitySetXML, &$params) {
152 foreach ($activitySetXML->ActivityTypes as $activityTypesXML) {
153 foreach ($activityTypesXML as $activityTypeXML) {
154 $this->createActivity($activityTypeXML, $params);
155 }
156 }
157 }
158
159 /**
160 * @param $caseRolesXML
161 * @param bool $isCaseManager
162 *
163 * @return array|mixed
164 */
165 public function &caseRoles($caseRolesXML, $isCaseManager = FALSE) {
166 // Look up relationship types according to the XML convention (described
167 // from perspective of non-client) but return the labels according to the UI
168 // convention (described from perspective of client)
169 $relationshipTypesToReturn = &$this->allRelationshipTypes(FALSE);
170
171 $result = [];
172 foreach ($caseRolesXML as $caseRoleXML) {
173 foreach ($caseRoleXML->RelationshipType as $relationshipTypeXML) {
174 list($relationshipTypeID,) = $this->locateNameOrLabel($relationshipTypeXML);
175 if ($relationshipTypeID === FALSE) {
176 continue;
177 }
178
179 if (!$isCaseManager) {
180 $result[$relationshipTypeID] = $relationshipTypesToReturn[$relationshipTypeID];
181 }
182 elseif ($relationshipTypeXML->manager == 1) {
183 return $relationshipTypeID;
184 }
185 }
186 }
187 return $result;
188 }
189
190 /**
191 * @param SimpleXMLElement $relationshipTypeXML
192 * @param array $params
193 *
194 * @return bool
195 * @throws CRM_Core_Exception
196 */
197 public function createRelationships($relationshipTypeXML, $params) {
198 // get the relationship
199 [$relationshipType, $relationshipTypeName] = $this->locateNameOrLabel($relationshipTypeXML);
200 if ($relationshipType === FALSE) {
201 $docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
202 throw new CRM_Core_Exception(ts('Relationship type %1, found in case configuration file, is not present in the database %2',
203 [1 => $relationshipTypeName, 2 => $docLink]
204 ));
205 }
206
207 $clients = (array) $params['clientID'];
208 $relationshipValues = [];
209
210 foreach ($clients as $clientId) {
211 // $relationshipType string ends in either `_a_b` or `_b_a`
212 $a = substr($relationshipType, -3, 1);
213 $b = substr($relationshipType, -1);
214 $relationshipValues[] = [
215 'relationship_type_id' => substr($relationshipType, 0, -4),
216 'is_active' => 1,
217 'case_id' => $params['caseID'],
218 'start_date' => date("Ymd"),
219 'end_date' => $params['relationship_end_date'] ?? NULL,
220 "contact_id_$a" => $clientId,
221 "contact_id_$b" => $params['creatorID'],
222 ];
223 }
224
225 //\Civi\Api4\Relationship::save(FALSE)
226 // ->setRecords($relationshipValues)
227 // ->setMatch(['case_id', 'relationship_type_id', 'contact_id_a', 'contact_id_b'])
228 // ->execute();
229 // FIXME: The above api code would be better, but doesn't work
230 // See discussion in https://github.com/civicrm/civicrm-core/pull/15030
231 foreach ($relationshipValues as $params) {
232 $dao = new CRM_Contact_DAO_Relationship();
233 $dao->copyValues($params);
234 // only create a relationship if it does not exist
235 if (!$dao->find(TRUE)) {
236 CRM_Contact_BAO_Relationship::add($params);
237 }
238 }
239
240 return TRUE;
241 }
242
243 /**
244 * @param $activityTypesXML
245 * @param bool $maxInst
246 * @param bool $isLabel
247 * @param bool $maskAction
248 *
249 * @return array
250 */
251 public function activityTypes($activityTypesXML, $maxInst = FALSE, $isLabel = FALSE, $maskAction = FALSE) {
252 $activityTypes = CRM_Case_PseudoConstant::caseActivityType(TRUE, TRUE);
253 $result = [];
254 foreach ($activityTypesXML as $activityTypeXML) {
255 foreach ($activityTypeXML as $recordXML) {
256 $activityTypeName = (string) $recordXML->name;
257 $maxInstances = (string) $recordXML->max_instances;
258 $activityTypeInfo = $activityTypes[$activityTypeName] ?? NULL;
259
260 if ($activityTypeInfo['id']) {
261 if ($maskAction) {
262 if ($maskAction == 'edit' && '0' === (string) $recordXML->editable) {
263 $result[$maskAction][] = $activityTypeInfo['id'];
264 }
265 }
266 else {
267 if (!$maxInst) {
268 //if we want,labels of activities should be returned.
269 if ($isLabel) {
270 $result[$activityTypeInfo['id']] = $activityTypeInfo['label'];
271 }
272 else {
273 $result[$activityTypeInfo['id']] = $activityTypeName;
274 }
275 }
276 else {
277 if ($maxInstances) {
278 $result[$activityTypeName] = $maxInstances;
279 }
280 }
281 }
282 }
283 }
284 }
285
286 // call option value hook
287 CRM_Utils_Hook::optionValues($result, 'case_activity_type');
288
289 return $result;
290 }
291
292 /**
293 * @param SimpleXMLElement $caseTypeXML
294 *
295 * @return array<string> symbolic activity-type names
296 */
297 public function getDeclaredActivityTypes($caseTypeXML) {
298 $result = [];
299
300 if (!empty($caseTypeXML->ActivityTypes) && $caseTypeXML->ActivityTypes->ActivityType) {
301 foreach ($caseTypeXML->ActivityTypes->ActivityType as $activityTypeXML) {
302 $result[] = (string) $activityTypeXML->name;
303 }
304 }
305
306 if (!empty($caseTypeXML->ActivitySets) && $caseTypeXML->ActivitySets->ActivitySet) {
307 foreach ($caseTypeXML->ActivitySets->ActivitySet as $activitySetXML) {
308 if ($activitySetXML->ActivityTypes && $activitySetXML->ActivityTypes->ActivityType) {
309 foreach ($activitySetXML->ActivityTypes->ActivityType as $activityTypeXML) {
310 $result[] = (string) $activityTypeXML->name;
311 }
312 }
313 }
314 }
315
316 $result = array_unique($result);
317 sort($result);
318 return $result;
319 }
320
321 /**
322 * Relationships are straight from XML, described from perspective of non-client
323 *
324 * @param SimpleXMLElement $caseTypeXML
325 *
326 * @return array<string> symbolic relationship-type names
327 */
328 public function getDeclaredRelationshipTypes($caseTypeXML) {
329 $result = [];
330
331 if (!empty($caseTypeXML->CaseRoles) && $caseTypeXML->CaseRoles->RelationshipType) {
332 foreach ($caseTypeXML->CaseRoles->RelationshipType as $relTypeXML) {
333 list(, $relationshipTypeMachineName) = $this->locateNameOrLabel($relTypeXML);
334 $result[] = $relationshipTypeMachineName;
335 }
336 }
337
338 $result = array_unique($result);
339 sort($result);
340 return $result;
341 }
342
343 /**
344 * @param array $params
345 */
346 public function deleteEmptyActivity(&$params) {
347 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
348 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
349
350 $query = "
351 DELETE a
352 FROM civicrm_activity a
353 INNER JOIN civicrm_activity_contact t ON t.activity_id = a.id
354 INNER JOIN civicrm_case_activity ca on ca.activity_id = a.id
355 WHERE t.contact_id = %1
356 AND t.record_type_id = $targetID
357 AND a.is_auto = 1
358 AND a.is_current_revision = 1
359 AND ca.case_id = %2
360 ";
361 $sqlParams = [1 => [$params['clientID'], 'Integer'], 2 => [$params['caseID'], 'Integer']];
362 CRM_Core_DAO::executeQuery($query, $sqlParams);
363 }
364
365 /**
366 * @param array $params
367 *
368 * @return bool
369 */
370 public function isActivityPresent(&$params) {
371 $query = "
372 SELECT count(a.id)
373 FROM civicrm_activity a
374 INNER JOIN civicrm_case_activity ca on ca.activity_id = a.id
375 WHERE a.activity_type_id = %1
376 AND ca.case_id = %2
377 AND a.is_deleted = 0
378 ";
379
380 $sqlParams = [
381 1 => [$params['activityTypeID'], 'Integer'],
382 2 => [$params['caseID'], 'Integer'],
383 ];
384 $count = CRM_Core_DAO::singleValueQuery($query, $sqlParams);
385
386 // check for max instance
387 $caseType = CRM_Case_BAO_Case::getCaseType($params['caseID'], 'name');
388 $maxInstance = self::getMaxInstance($caseType, $params['activityTypeName']);
389
390 return $maxInstance ? ($count < $maxInstance ? FALSE : TRUE) : FALSE;
391 }
392
393 /**
394 * @param $activityTypeXML
395 * @param array $params
396 *
397 * @return bool
398 * @throws CRM_Core_Exception
399 * @throws Exception
400 */
401 public function createActivity($activityTypeXML, &$params) {
402 $activityTypeName = (string) $activityTypeXML->name;
403 $activityTypes = CRM_Case_PseudoConstant::caseActivityType(TRUE, TRUE);
404 $activityTypeInfo = $activityTypes[$activityTypeName] ?? NULL;
405
406 if (!$activityTypeInfo) {
407 $docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
408 throw new CRM_Core_Exception(ts('Activity type %1, found in case configuration file, is not present in the database %2',
409 [1 => $activityTypeName, 2 => $docLink]
410 ));
411 }
412
413 $activityTypeID = $activityTypeInfo['id'];
414
415 if (isset($activityTypeXML->status)) {
416 $statusName = (string) $activityTypeXML->status;
417 }
418 else {
419 $statusName = 'Scheduled';
420 }
421
422 $client = (array) $params['clientID'];
423
424 //set order
425 $orderVal = '';
426 if (isset($activityTypeXML->order)) {
427 $orderVal = (string) $activityTypeXML->order;
428 }
429
430 if ($activityTypeName == 'Open Case') {
431 $activityParams = [
432 'activity_type_id' => $activityTypeID,
433 'source_contact_id' => $params['creatorID'],
434 'is_auto' => FALSE,
435 'is_current_revision' => 1,
436 'subject' => !empty($params['subject']) ? $params['subject'] : $activityTypeName,
437 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', $statusName),
438 'target_contact_id' => $client,
439 'medium_id' => $params['medium_id'] ?? NULL,
440 'location' => $params['location'] ?? NULL,
441 'details' => $params['details'] ?? NULL,
442 'duration' => $params['duration'] ?? NULL,
443 'weight' => $orderVal,
444 ];
445 }
446 else {
447 $activityParams = [
448 'activity_type_id' => $activityTypeID,
449 'source_contact_id' => $params['creatorID'],
450 'is_auto' => TRUE,
451 'is_current_revision' => 1,
452 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', $statusName),
453 'target_contact_id' => $client,
454 'weight' => $orderVal,
455 ];
456 }
457
458 $activityParams['assignee_contact_id'] = $this->getDefaultAssigneeForActivity($activityParams, $activityTypeXML, $params['caseID']);
459
460 //parsing date to default preference format
461 $params['activity_date_time'] = CRM_Utils_Date::processDate($params['activity_date_time']);
462
463 if ($activityTypeName == 'Open Case') {
464 // we don't set activity_date_time for auto generated
465 // activities, but we want it to be set for open case.
466 $activityParams['activity_date_time'] = $params['activity_date_time'];
467 if (array_key_exists('custom', $params) && is_array($params['custom'])) {
468 $activityParams['custom'] = $params['custom'];
469 }
470
471 // Add parameters for attachments
472
473 $numAttachments = Civi::settings()->get('max_attachments');
474 for ($i = 1; $i <= $numAttachments; $i++) {
475 $attachName = "attachFile_$i";
476 if (isset($params[$attachName]) && !empty($params[$attachName])) {
477 $activityParams[$attachName] = $params[$attachName];
478 }
479 }
480 }
481 else {
482 $activityDate = NULL;
483 //get date of reference activity if set.
484 if ($referenceActivityName = (string) $activityTypeXML->reference_activity) {
485
486 //we skip open case as reference activity.CRM-4374.
487 if (!empty($params['resetTimeline']) && $referenceActivityName == 'Open Case') {
488 $activityDate = $params['activity_date_time'];
489 }
490 else {
491 $referenceActivityInfo = $activityTypes[$referenceActivityName] ?? NULL;
492 if ($referenceActivityInfo['id']) {
493 $caseActivityParams = ['activity_type_id' => $referenceActivityInfo['id']];
494
495 //if reference_select is set take according activity.
496 if ($referenceSelect = (string) $activityTypeXML->reference_select) {
497 $caseActivityParams[$referenceSelect] = 1;
498 }
499
500 $referenceActivity = CRM_Case_BAO_Case::getCaseActivityDates($params['caseID'], $caseActivityParams, TRUE);
501
502 if (is_array($referenceActivity)) {
503 foreach ($referenceActivity as $aId => $details) {
504 $activityDate = $details['activity_date'] ?? NULL;
505 break;
506 }
507 }
508 }
509 }
510 }
511 if (!$activityDate) {
512 $activityDate = $params['activity_date_time'];
513 }
514 list($activity_date, $activity_time) = CRM_Utils_Date::setDateDefaults($activityDate);
515 $activityDateTime = CRM_Utils_Date::processDate($activity_date, $activity_time);
516 //add reference offset to date.
517 if ((int) $activityTypeXML->reference_offset) {
518 $activityDateTime = CRM_Utils_Date::intervalAdd('day', (int) $activityTypeXML->reference_offset,
519 $activityDateTime
520 );
521 }
522
523 $activityParams['activity_date_time'] = CRM_Utils_Date::format($activityDateTime);
524 }
525
526 // if same activity is already there, skip and dont touch
527 $params['activityTypeID'] = $activityTypeID;
528 $params['activityTypeName'] = $activityTypeName;
529 if ($this->isActivityPresent($params)) {
530 return TRUE;
531 }
532 $activityParams['case_id'] = $params['caseID'];
533 if (!empty($activityParams['is_auto'])) {
534 $activityParams['skipRecentView'] = TRUE;
535 }
536
537 // @todo - switch to using api & remove the parameter pre-wrangling above.
538 $activity = CRM_Activity_BAO_Activity::create($activityParams);
539
540 if (!$activity) {
541 throw new CRM_Core_Exception('Unable to create Activity');
542 }
543
544 // create case activity record
545 $caseParams = [
546 'activity_id' => $activity->id,
547 'case_id' => $params['caseID'],
548 ];
549 CRM_Case_BAO_Case::processCaseActivity($caseParams);
550 return TRUE;
551 }
552
553 /**
554 * Return the default assignee contact for the activity.
555 *
556 * @param array $activityParams
557 * @param object $activityTypeXML
558 * @param int $caseId
559 *
560 * @return int|null the ID of the default assignee contact or null if none.
561 */
562 protected function getDefaultAssigneeForActivity($activityParams, $activityTypeXML, $caseId) {
563 if (!isset($activityTypeXML->default_assignee_type)) {
564 return NULL;
565 }
566
567 $defaultAssigneeOptionsValues = $this->getDefaultAssigneeOptionValues();
568
569 switch ($activityTypeXML->default_assignee_type) {
570 case $defaultAssigneeOptionsValues['BY_RELATIONSHIP']:
571 return $this->getDefaultAssigneeByRelationship($activityParams, $activityTypeXML, $caseId);
572
573 break;
574 case $defaultAssigneeOptionsValues['SPECIFIC_CONTACT']:
575 return $this->getDefaultAssigneeBySpecificContact($activityTypeXML);
576
577 break;
578 case $defaultAssigneeOptionsValues['USER_CREATING_THE_CASE']:
579 return $activityParams['source_contact_id'];
580
581 break;
582 case $defaultAssigneeOptionsValues['NONE']:
583 default:
584 return NULL;
585 }
586 }
587
588 /**
589 * Fetches and caches the activity's default assignee options.
590 *
591 * @return array
592 */
593 protected function getDefaultAssigneeOptionValues() {
594 if (!empty($this->defaultAssigneeOptionsValues)) {
595 return $this->defaultAssigneeOptionsValues;
596 }
597
598 $defaultAssigneeOptions = civicrm_api3('OptionValue', 'get', [
599 'option_group_id' => 'activity_default_assignee',
600 'options' => ['limit' => 0],
601 ]);
602
603 foreach ($defaultAssigneeOptions['values'] as $option) {
604 $this->defaultAssigneeOptionsValues[$option['name']] = $option['value'];
605 }
606
607 return $this->defaultAssigneeOptionsValues;
608 }
609
610 /**
611 * Returns the default assignee for the activity by searching for the target's
612 * contact relationship type defined in the activity's details.
613 *
614 * @param array $activityParams
615 * @param object $activityTypeXML
616 * @param int $caseId
617 *
618 * @return int|null the ID of the default assignee contact or null if none.
619 */
620 protected function getDefaultAssigneeByRelationship($activityParams, $activityTypeXML, $caseId) {
621 $isDefaultRelationshipDefined = isset($activityTypeXML->default_assignee_relationship)
622 && preg_match('/\d+_[ab]_[ab]/', $activityTypeXML->default_assignee_relationship);
623
624 if (!$isDefaultRelationshipDefined) {
625 return NULL;
626 }
627
628 $targetContactId = is_array($activityParams['target_contact_id'])
629 ? CRM_Utils_Array::first($activityParams['target_contact_id'])
630 : $activityParams['target_contact_id'];
631 list($relTypeId, $a, $b) = explode('_', $activityTypeXML->default_assignee_relationship);
632
633 $params = [
634 'relationship_type_id' => $relTypeId,
635 "contact_id_$b" => $targetContactId,
636 'is_active' => 1,
637 'case_id' => $caseId,
638 'options' => ['limit' => 1],
639 ];
640
641 if ($this->isBidirectionalRelationshipType($relTypeId)) {
642 $params["contact_id_$a"] = $targetContactId;
643 $params['options']['or'] = [['contact_id_a', 'contact_id_b']];
644 }
645
646 $relationships = civicrm_api3('Relationship', 'get', $params);
647 if (empty($relationships['count'])) {
648 $params['case_id'] = ['IS NULL' => 1];
649 $relationships = civicrm_api3('Relationship', 'get', $params);
650 }
651
652 if ($relationships['count']) {
653 $relationship = CRM_Utils_Array::first($relationships['values']);
654
655 // returns the contact id on the other side of the relationship:
656 return (int) $relationship['contact_id_a'] === (int) $targetContactId
657 ? $relationship['contact_id_b']
658 : $relationship['contact_id_a'];
659 }
660 else {
661 return NULL;
662 }
663 }
664
665 /**
666 * Determines if the given relationship type is bidirectional or not by
667 * comparing their labels.
668 *
669 * @return bool
670 */
671 protected function isBidirectionalRelationshipType($relationshipTypeId) {
672 $relationshipTypeResult = civicrm_api3('RelationshipType', 'get', [
673 'id' => $relationshipTypeId,
674 'options' => ['limit' => 1],
675 ]);
676
677 if ($relationshipTypeResult['count'] === 0) {
678 return FALSE;
679 }
680
681 $relationshipType = CRM_Utils_Array::first($relationshipTypeResult['values']);
682
683 return $relationshipType['label_b_a'] === $relationshipType['label_a_b'];
684 }
685
686 /**
687 * Returns the activity's default assignee for a specific contact if the contact exists,
688 * otherwise returns null.
689 *
690 * @param object $activityTypeXML
691 *
692 * @return int|null
693 */
694 protected function getDefaultAssigneeBySpecificContact($activityTypeXML) {
695 if (!$activityTypeXML->default_assignee_contact) {
696 return NULL;
697 }
698
699 $contact = civicrm_api3('Contact', 'get', [
700 'id' => $activityTypeXML->default_assignee_contact,
701 ]);
702
703 if ($contact['count'] == 1) {
704 return $activityTypeXML->default_assignee_contact;
705 }
706
707 return NULL;
708 }
709
710 /**
711 * @param $activitySetsXML
712 *
713 * @return array
714 */
715 public static function activitySets($activitySetsXML) {
716 $result = [];
717 foreach ($activitySetsXML as $activitySetXML) {
718 foreach ($activitySetXML as $recordXML) {
719 $activitySetName = (string) $recordXML->name;
720 $activitySetLabel = (string) $recordXML->label;
721 $result[$activitySetName] = $activitySetLabel;
722 }
723 }
724
725 return $result;
726 }
727
728 /**
729 * @param $caseType
730 * @param string|null $activityTypeName
731 *
732 * @return array|bool|mixed
733 * @throws CRM_Core_Exception
734 */
735 public function getMaxInstance($caseType, $activityTypeName = NULL) {
736 $xml = $this->retrieve($caseType);
737
738 if ($xml === FALSE) {
739 throw new CRM_Core_Exception('Unable to locate xml definition for case type ' . $caseType);
740 }
741
742 $activityInstances = $this->activityTypes($xml->ActivityTypes, TRUE);
743 return $activityTypeName ? CRM_Utils_Array::value($activityTypeName, $activityInstances) : $activityInstances;
744 }
745
746 /**
747 * @param $caseType
748 *
749 * @return array|mixed
750 */
751 public function getCaseManagerRoleId($caseType) {
752 $xml = $this->retrieve($caseType);
753 return $this->caseRoles($xml->CaseRoles, TRUE);
754 }
755
756 /**
757 * @param string $caseType
758 *
759 * @return array<\Civi\CCase\CaseChangeListener>
760 */
761 public function getListeners($caseType) {
762 $xml = $this->retrieve($caseType);
763 $listeners = [];
764 if ($xml->Listeners && $xml->Listeners->Listener) {
765 foreach ($xml->Listeners->Listener as $listenerXML) {
766 $class = (string) $listenerXML;
767 $listeners[] = new $class();
768 }
769 }
770 return $listeners;
771 }
772
773 /**
774 * @return int
775 */
776 public function getRedactActivityEmail() {
777 return $this->getBoolSetting('civicaseRedactActivityEmail', 'RedactActivityEmail');
778 }
779
780 /**
781 * Retrieves AllowMultipleCaseClients setting.
782 *
783 * @return string
784 * 1 if allowed, 0 if not
785 */
786 public function getAllowMultipleCaseClients() {
787 return $this->getBoolSetting('civicaseAllowMultipleClients', 'AllowMultipleCaseClients');
788 }
789
790 /**
791 * Retrieves NaturalActivityTypeSort setting.
792 *
793 * @return string
794 * 1 if natural, 0 if alphabetic
795 */
796 public function getNaturalActivityTypeSort() {
797 return $this->getBoolSetting('civicaseNaturalActivityTypeSort', 'NaturalActivityTypeSort');
798 }
799
800 /**
801 * @param string $settingKey
802 * @param string $xmlTag
803 * @param mixed $default
804 *
805 * @return int
806 */
807 private function getBoolSetting($settingKey, $xmlTag, $default = 0) {
808 $setting = Civi::settings()->get($settingKey);
809 if ($setting !== 'default') {
810 return (int) $setting;
811 }
812 if ($xml = $this->retrieve("Settings")) {
813 return (string) $xml->{$xmlTag} ? 1 : 0;
814 }
815 return $default;
816 }
817
818 /**
819 * At some point name and label got mixed up for case roles.
820 * Check against known machine name values, and then if no match check
821 * against labels.
822 * This is subject to some edge cases, but we catch those with a system
823 * status check.
824 * We do this to avoid requiring people to update their xml files which can
825 * be stored in external files we can't/don't want to edit.
826 *
827 * @param SimpleXMLElement $xml
828 *
829 * @return array[bool|string,string]
830 */
831 public function locateNameOrLabel($xml) {
832 $lookupString = (string) $xml->name;
833
834 // Don't use pseudoconstant because we need everything both name and
835 // label and disabled types.
836 $relationshipTypes = civicrm_api3('RelationshipType', 'get', [
837 'options' => ['limit' => 0],
838 ])['values'];
839
840 // First look and see if it matches a machine name in the system.
841 // There are some edge cases here where we've actually been passed in a
842 // display label and it happens to match the machine name for a different
843 // db entry, but we have a system status check.
844 // But, we do want to check against the a_b version first, because of the
845 // way direction matters and that for bidirectional only one is present in
846 // the list where this eventually gets used, so return that first.
847 $relationshipTypeMachineNames = array_column($relationshipTypes, 'id', 'name_a_b');
848 if (isset($relationshipTypeMachineNames[$lookupString])) {
849 return ["{$relationshipTypeMachineNames[$lookupString]}_b_a", $lookupString];
850 }
851 $relationshipTypeMachineNames = array_column($relationshipTypes, 'id', 'name_b_a');
852 if (isset($relationshipTypeMachineNames[$lookupString])) {
853 return ["{$relationshipTypeMachineNames[$lookupString]}_a_b", $lookupString];
854 }
855
856 // Now at this point assume we've been passed a display label, so find
857 // what it matches and return the associated machine name. This is a bit
858 // trickier because suppose somebody has changed the display labels so
859 // that they are now the same, but the machine names are different. We
860 // don't know which to return and so while it's the right relationship type
861 // it might be the backwards direction. We have to pick one to try first.
862
863 $relationshipTypeDisplayLabels = array_column($relationshipTypes, 'id', 'label_a_b');
864 if (isset($relationshipTypeDisplayLabels[$lookupString])) {
865 return [
866 "{$relationshipTypeDisplayLabels[$lookupString]}_b_a",
867 $relationshipTypes[$relationshipTypeDisplayLabels[$lookupString]]['name_a_b'],
868 ];
869 }
870 $relationshipTypeDisplayLabels = array_column($relationshipTypes, 'id', 'label_b_a');
871 if (isset($relationshipTypeDisplayLabels[$lookupString])) {
872 return [
873 "{$relationshipTypeDisplayLabels[$lookupString]}_a_b",
874 $relationshipTypes[$relationshipTypeDisplayLabels[$lookupString]]['name_b_a'],
875 ];
876 }
877
878 // Just go with what we were passed in, even though it doesn't seem
879 // to match *anything*. This was what it did before.
880 return [FALSE, $lookupString];
881 }
882
883 }