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