3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2018
33 class CRM_Case_XMLProcessor_Process
extends CRM_Case_XMLProcessor
{
34 protected $defaultAssigneeOptionsValues = [];
39 * @param string $caseType
40 * @param array $params
45 public function run($caseType, &$params) {
46 $xml = $this->retrieve($caseType);
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 array(1 => $caseType, 2 => $docLink)
56 $xmlProcessorProcess = new CRM_Case_XMLProcessor_Process();
57 $this->_isMultiClient
= $xmlProcessorProcess->getAllowMultipleCaseClients();
59 $this->process($xml, $params);
65 * @param bool $isLabel
66 * @param bool $maskAction
68 * @return array|bool|mixed
71 public function get($caseType, $fieldSet, $isLabel = FALSE, $maskAction = FALSE) {
72 $xml = $this->retrieve($caseType);
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 array(1 => $caseType, 2 => $docLink)
83 return $this->caseRoles($xml->CaseRoles
);
86 return $this->activitySets($xml->ActivitySets
);
89 return $this->activityTypes($xml->ActivityTypes
, FALSE, $isLabel, $maskAction);
95 * @param array $params
99 public function process($xml, &$params) {
100 $standardTimeline = CRM_Utils_Array
::value('standardTimeline', $params);
101 $activitySetName = CRM_Utils_Array
::value('activitySetName', $params);
102 $activityTypeName = CRM_Utils_Array
::value('activityTypeName', $params);
104 if ('Open Case' == CRM_Utils_Array
::value('activityTypeName', $params)) {
105 // create relationships for the ones that are required
106 foreach ($xml->CaseRoles
as $caseRoleXML) {
107 foreach ($caseRoleXML->RelationshipType
as $relationshipTypeXML) {
108 if ((int ) $relationshipTypeXML->creator
== 1) {
109 if (!$this->createRelationships((string ) $relationshipTypeXML->name
,
113 CRM_Core_Error
::fatal();
121 if ('Change Case Start Date' == CRM_Utils_Array
::value('activityTypeName', $params)) {
122 // delete all existing activities which are non-empty
123 $this->deleteEmptyActivity($params);
126 foreach ($xml->ActivitySets
as $activitySetsXML) {
127 foreach ($activitySetsXML->ActivitySet
as $activitySetXML) {
128 if ($standardTimeline) {
129 if ((boolean
) $activitySetXML->timeline
) {
130 return $this->processStandardTimeline($activitySetXML,
135 elseif ($activitySetName) {
136 $name = (string ) $activitySetXML->name
;
137 if ($name == $activitySetName) {
138 return $this->processActivitySet($activitySetXML,
148 * @param $activitySetXML
149 * @param array $params
151 public function processStandardTimeline($activitySetXML, &$params) {
152 if ('Change Case Type' == CRM_Utils_Array
::value('activityTypeName', $params)
153 && CRM_Utils_Array
::value('resetTimeline', $params, TRUE)
155 // delete all existing activities which are non-empty
156 $this->deleteEmptyActivity($params);
159 foreach ($activitySetXML->ActivityTypes
as $activityTypesXML) {
160 foreach ($activityTypesXML as $activityTypeXML) {
161 $this->createActivity($activityTypeXML, $params);
167 * @param $activitySetXML
168 * @param array $params
170 public function processActivitySet($activitySetXML, &$params) {
171 foreach ($activitySetXML->ActivityTypes
as $activityTypesXML) {
172 foreach ($activityTypesXML as $activityTypeXML) {
173 $this->createActivity($activityTypeXML, $params);
179 * @param $caseRolesXML
180 * @param bool $isCaseManager
182 * @return array|mixed
184 public function &caseRoles($caseRolesXML, $isCaseManager = FALSE) {
185 $relationshipTypes = &$this->allRelationshipTypes();
188 foreach ($caseRolesXML as $caseRoleXML) {
189 foreach ($caseRoleXML->RelationshipType
as $relationshipTypeXML) {
190 $relationshipTypeName = (string ) $relationshipTypeXML->name
;
191 $relationshipTypeID = array_search($relationshipTypeName,
194 if ($relationshipTypeID === FALSE) {
198 if (!$isCaseManager) {
199 $result[$relationshipTypeID] = $relationshipTypeName;
201 elseif ($relationshipTypeXML->manager
) {
202 return $relationshipTypeID;
210 * @param string $relationshipTypeName
211 * @param array $params
216 public function createRelationships($relationshipTypeName, &$params) {
217 $relationshipTypes = &$this->allRelationshipTypes();
218 // get the relationship id
219 $relationshipTypeID = array_search($relationshipTypeName, $relationshipTypes);
221 if ($relationshipTypeID === FALSE) {
222 $docLink = CRM_Utils_System
::docURL2("user/case-management/set-up");
223 CRM_Core_Error
::fatal(ts('Relationship type %1, found in case configuration file, is not present in the database %2',
224 array(1 => $relationshipTypeName, 2 => $docLink)
229 $client = $params['clientID'];
230 if (!is_array($client)) {
231 $client = array($client);
234 foreach ($client as $key => $clientId) {
235 $relationshipParams = array(
236 'relationship_type_id' => $relationshipTypeID,
237 'contact_id_a' => $clientId,
238 'contact_id_b' => $params['creatorID'],
240 'case_id' => $params['caseID'],
241 'start_date' => date("Ymd"),
242 'end_date' => CRM_Utils_Array
::value('relationship_end_date', $params),
245 if (!$this->createRelationship($relationshipParams)) {
246 CRM_Core_Error
::fatal();
254 * @param array $params
258 public function createRelationship(&$params) {
259 $dao = new CRM_Contact_DAO_Relationship();
260 $dao->copyValues($params);
261 // only create a relationship if it does not exist
262 if (!$dao->find(TRUE)) {
269 * @param $activityTypesXML
270 * @param bool $maxInst
271 * @param bool $isLabel
272 * @param bool $maskAction
276 public function activityTypes($activityTypesXML, $maxInst = FALSE, $isLabel = FALSE, $maskAction = FALSE) {
277 $activityTypes = &$this->allActivityTypes(TRUE, TRUE);
279 foreach ($activityTypesXML as $activityTypeXML) {
280 foreach ($activityTypeXML as $recordXML) {
281 $activityTypeName = (string ) $recordXML->name
;
282 $maxInstances = (string ) $recordXML->max_instances
;
283 $activityTypeInfo = CRM_Utils_Array
::value($activityTypeName, $activityTypes);
285 if ($activityTypeInfo['id']) {
287 if ($maskAction == 'edit' && '0' === (string ) $recordXML->editable
) {
288 $result[$maskAction][] = $activityTypeInfo['id'];
293 //if we want,labels of activities should be returned.
295 $result[$activityTypeInfo['id']] = $activityTypeInfo['label'];
298 $result[$activityTypeInfo['id']] = $activityTypeName;
303 $result[$activityTypeName] = $maxInstances;
311 // call option value hook
312 CRM_Utils_Hook
::optionValues($result, 'case_activity_type');
318 * @param SimpleXMLElement $caseTypeXML
320 * @return array<string> symbolic activity-type names
322 public function getDeclaredActivityTypes($caseTypeXML) {
325 if (!empty($caseTypeXML->ActivityTypes
) && $caseTypeXML->ActivityTypes
->ActivityType
) {
326 foreach ($caseTypeXML->ActivityTypes
->ActivityType
as $activityTypeXML) {
327 $result[] = (string) $activityTypeXML->name
;
331 if (!empty($caseTypeXML->ActivitySets
) && $caseTypeXML->ActivitySets
->ActivitySet
) {
332 foreach ($caseTypeXML->ActivitySets
->ActivitySet
as $activitySetXML) {
333 if ($activitySetXML->ActivityTypes
&& $activitySetXML->ActivityTypes
->ActivityType
) {
334 foreach ($activitySetXML->ActivityTypes
->ActivityType
as $activityTypeXML) {
335 $result[] = (string) $activityTypeXML->name
;
341 $result = array_unique($result);
347 * @param SimpleXMLElement $caseTypeXML
349 * @return array<string> symbolic relationship-type names
351 public function getDeclaredRelationshipTypes($caseTypeXML) {
354 if (!empty($caseTypeXML->CaseRoles
) && $caseTypeXML->CaseRoles
->RelationshipType
) {
355 foreach ($caseTypeXML->CaseRoles
->RelationshipType
as $relTypeXML) {
356 $result[] = (string) $relTypeXML->name
;
360 $result = array_unique($result);
366 * @param array $params
368 public function deleteEmptyActivity(&$params) {
369 $activityContacts = CRM_Activity_BAO_ActivityContact
::buildOptions('record_type_id', 'validate');
370 $targetID = CRM_Utils_Array
::key('Activity Targets', $activityContacts);
374 FROM civicrm_activity a
375 INNER JOIN civicrm_activity_contact t ON t.activity_id = a.id
376 INNER JOIN civicrm_case_activity ca on ca.activity_id = a.id
377 WHERE t.contact_id = %1
378 AND t.record_type_id = $targetID
380 AND a.is_current_revision = 1
383 $sqlParams = array(1 => array($params['clientID'], 'Integer'), 2 => array($params['caseID'], 'Integer'));
384 CRM_Core_DAO
::executeQuery($query, $sqlParams);
388 * @param array $params
392 public function isActivityPresent(&$params) {
395 FROM civicrm_activity a
396 INNER JOIN civicrm_case_activity ca on ca.activity_id = a.id
397 WHERE a.activity_type_id = %1
403 1 => array($params['activityTypeID'], 'Integer'),
404 2 => array($params['caseID'], 'Integer'),
406 $count = CRM_Core_DAO
::singleValueQuery($query, $sqlParams);
408 // check for max instance
409 $caseType = CRM_Case_BAO_Case
::getCaseType($params['caseID'], 'name');
410 $maxInstance = self
::getMaxInstance($caseType, $params['activityTypeName']);
412 return $maxInstance ?
($count < $maxInstance ?
FALSE : TRUE) : FALSE;
416 * @param $activityTypeXML
417 * @param array $params
420 * @throws CRM_Core_Exception
423 public function createActivity($activityTypeXML, &$params) {
424 $activityTypeName = (string) $activityTypeXML->name
;
425 $activityTypes = &$this->allActivityTypes(TRUE, TRUE);
426 $activityTypeInfo = CRM_Utils_Array
::value($activityTypeName, $activityTypes);
428 if (!$activityTypeInfo) {
429 $docLink = CRM_Utils_System
::docURL2("user/case-management/set-up");
430 CRM_Core_Error
::fatal(ts('Activity type %1, found in case configuration file, is not present in the database %2',
431 array(1 => $activityTypeName, 2 => $docLink)
436 $activityTypeID = $activityTypeInfo['id'];
438 if (isset($activityTypeXML->status
)) {
439 $statusName = (string) $activityTypeXML->status
;
442 $statusName = 'Scheduled';
445 $client = (array) $params['clientID'];
449 if (isset($activityTypeXML->order
)) {
450 $orderVal = (string) $activityTypeXML->order
;
453 if ($activityTypeName == 'Open Case') {
454 $activityParams = array(
455 'activity_type_id' => $activityTypeID,
456 'source_contact_id' => $params['creatorID'],
458 'is_current_revision' => 1,
459 'subject' => CRM_Utils_Array
::value('subject', $params) ?
$params['subject'] : $activityTypeName,
460 'status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', $statusName),
461 'target_contact_id' => $client,
462 'medium_id' => CRM_Utils_Array
::value('medium_id', $params),
463 'location' => CRM_Utils_Array
::value('location', $params),
464 'details' => CRM_Utils_Array
::value('details', $params),
465 'duration' => CRM_Utils_Array
::value('duration', $params),
466 'weight' => $orderVal,
470 $activityParams = array(
471 'activity_type_id' => $activityTypeID,
472 'source_contact_id' => $params['creatorID'],
474 'is_current_revision' => 1,
475 'status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', $statusName),
476 'target_contact_id' => $client,
477 'weight' => $orderVal,
481 $activityParams['assignee_contact_id'] = $this->getDefaultAssigneeForActivity($activityParams, $activityTypeXML);
483 //parsing date to default preference format
484 $params['activity_date_time'] = CRM_Utils_Date
::processDate($params['activity_date_time']);
486 if ($activityTypeName == 'Open Case') {
487 // we don't set activity_date_time for auto generated
488 // activities, but we want it to be set for open case.
489 $activityParams['activity_date_time'] = $params['activity_date_time'];
490 if (array_key_exists('custom', $params) && is_array($params['custom'])) {
491 $activityParams['custom'] = $params['custom'];
494 // Add parameters for attachments
496 $numAttachments = Civi
::settings()->get('max_attachments');
497 for ($i = 1; $i <= $numAttachments; $i++
) {
498 $attachName = "attachFile_$i";
499 if (isset($params[$attachName]) && !empty($params[$attachName])) {
500 $activityParams[$attachName] = $params[$attachName];
505 $activityDate = NULL;
506 //get date of reference activity if set.
507 if ($referenceActivityName = (string) $activityTypeXML->reference_activity
) {
509 //we skip open case as reference activity.CRM-4374.
510 if (!empty($params['resetTimeline']) && $referenceActivityName == 'Open Case') {
511 $activityDate = $params['activity_date_time'];
514 $referenceActivityInfo = CRM_Utils_Array
::value($referenceActivityName, $activityTypes);
515 if ($referenceActivityInfo['id']) {
516 $caseActivityParams = array('activity_type_id' => $referenceActivityInfo['id']);
518 //if reference_select is set take according activity.
519 if ($referenceSelect = (string) $activityTypeXML->reference_select
) {
520 $caseActivityParams[$referenceSelect] = 1;
523 $referenceActivity = CRM_Case_BAO_Case
::getCaseActivityDates($params['caseID'], $caseActivityParams, TRUE);
525 if (is_array($referenceActivity)) {
526 foreach ($referenceActivity as $aId => $details) {
527 $activityDate = CRM_Utils_Array
::value('activity_date', $details);
534 if (!$activityDate) {
535 $activityDate = $params['activity_date_time'];
537 list($activity_date, $activity_time) = CRM_Utils_Date
::setDateDefaults($activityDate);
538 $activityDateTime = CRM_Utils_Date
::processDate($activity_date, $activity_time);
539 //add reference offset to date.
540 if ((int) $activityTypeXML->reference_offset
) {
541 $activityDateTime = CRM_Utils_Date
::intervalAdd('day', (int) $activityTypeXML->reference_offset
,
546 $activityParams['activity_date_time'] = CRM_Utils_Date
::format($activityDateTime);
549 // if same activity is already there, skip and dont touch
550 $params['activityTypeID'] = $activityTypeID;
551 $params['activityTypeName'] = $activityTypeName;
552 if ($this->isActivityPresent($params)) {
555 $activityParams['case_id'] = $params['caseID'];
556 if (!empty($activityParams['is_auto'])) {
557 $activityParams['skipRecentView'] = TRUE;
560 // @todo - switch to using api & remove the parameter pre-wrangling above.
561 $activity = CRM_Activity_BAO_Activity
::create($activityParams);
564 CRM_Core_Error
::fatal();
568 // create case activity record
570 'activity_id' => $activity->id
,
571 'case_id' => $params['caseID'],
573 CRM_Case_BAO_Case
::processCaseActivity($caseParams);
578 * Return the default assignee contact for the activity.
580 * @param array $activityParams
581 * @param object $activityTypeXML
583 * @return int|null the ID of the default assignee contact or null if none.
585 protected function getDefaultAssigneeForActivity($activityParams, $activityTypeXML) {
586 if (!isset($activityTypeXML->default_assignee_type
)) {
590 $defaultAssigneeOptionsValues = $this->getDefaultAssigneeOptionValues();
592 switch ($activityTypeXML->default_assignee_type
) {
593 case $defaultAssigneeOptionsValues['BY_RELATIONSHIP']:
594 return $this->getDefaultAssigneeByRelationship($activityParams, $activityTypeXML);
597 case $defaultAssigneeOptionsValues['SPECIFIC_CONTACT']:
598 return $this->getDefaultAssigneeBySpecificContact($activityTypeXML);
601 case $defaultAssigneeOptionsValues['USER_CREATING_THE_CASE']:
602 return $activityParams['source_contact_id'];
605 case $defaultAssigneeOptionsValues['NONE']:
612 * Fetches and caches the activity's default assignee options.
616 protected function getDefaultAssigneeOptionValues() {
617 if (!empty($this->defaultAssigneeOptionsValues
)) {
618 return $this->defaultAssigneeOptionsValues
;
621 $defaultAssigneeOptions = civicrm_api3('OptionValue', 'get', [
622 'option_group_id' => 'activity_default_assignee',
623 'options' => [ 'limit' => 0 ]
626 foreach ($defaultAssigneeOptions['values'] as $option) {
627 $this->defaultAssigneeOptionsValues
[$option['name']] = $option['value'];
630 return $this->defaultAssigneeOptionsValues
;
634 * Returns the default assignee for the activity by searching for the target's
635 * contact relationship type defined in the activity's details.
637 * @param array $activityParams
638 * @param object $activityTypeXML
640 * @return int|null the ID of the default assignee contact or null if none.
642 protected function getDefaultAssigneeByRelationship($activityParams, $activityTypeXML) {
643 $isDefaultRelationshipDefined = isset($activityTypeXML->default_assignee_relationship
)
644 && preg_match('/\d+_[ab]_[ab]/', $activityTypeXML->default_assignee_relationship
);
646 if (!$isDefaultRelationshipDefined) {
650 $targetContactId = is_array($activityParams['target_contact_id'])
651 ? CRM_Utils_Array
::first($activityParams['target_contact_id'])
652 : $activityParams['target_contact_id'];
653 list($relTypeId, $a, $b) = explode('_', $activityTypeXML->default_assignee_relationship
);
656 'relationship_type_id' => $relTypeId,
657 "contact_id_$b" => $targetContactId,
661 if ($this->isBidirectionalRelationshipType($relTypeId)) {
662 $params["contact_id_$a"] = $targetContactId;
663 $params['options']['or'] = [['contact_id_a', 'contact_id_b']];
666 $relationships = civicrm_api3('Relationship', 'get', $params);
668 if ($relationships['count']) {
669 $relationship = CRM_Utils_Array
::first($relationships['values']);
671 // returns the contact id on the other side of the relationship:
672 return (int) $relationship['contact_id_a'] === (int) $targetContactId
673 ?
$relationship['contact_id_b']
674 : $relationship['contact_id_a'];
682 * Determines if the given relationship type is bidirectional or not by
683 * comparing their labels.
687 protected function isBidirectionalRelationshipType($relationshipTypeId) {
688 $relationshipTypeResult = civicrm_api3('RelationshipType', 'get', [
689 'id' => $relationshipTypeId,
690 'options' => ['limit' => 1]
693 if ($relationshipTypeResult['count'] === 0) {
697 $relationshipType = CRM_Utils_Array
::first($relationshipTypeResult['values']);
699 return $relationshipType['label_b_a'] === $relationshipType['label_a_b'];
703 * Returns the activity's default assignee for a specific contact if the contact exists,
704 * otherwise returns null.
706 * @param object $activityTypeXML
710 protected function getDefaultAssigneeBySpecificContact($activityTypeXML) {
711 if (!$activityTypeXML->default_assignee_contact
) {
715 $contact = civicrm_api3('Contact', 'get', [
716 'id' => $activityTypeXML->default_assignee_contact
719 if ($contact['count'] == 1) {
720 return $activityTypeXML->default_assignee_contact
;
727 * @param $activitySetsXML
731 public static function activitySets($activitySetsXML) {
733 foreach ($activitySetsXML as $activitySetXML) {
734 foreach ($activitySetXML as $recordXML) {
735 $activitySetName = (string ) $recordXML->name
;
736 $activitySetLabel = (string ) $recordXML->label
;
737 $result[$activitySetName] = $activitySetLabel;
746 * @param null $activityTypeName
748 * @return array|bool|mixed
751 public function getMaxInstance($caseType, $activityTypeName = NULL) {
752 $xml = $this->retrieve($caseType);
754 if ($xml === FALSE) {
755 CRM_Core_Error
::fatal();
759 $activityInstances = $this->activityTypes($xml->ActivityTypes
, TRUE);
760 return $activityTypeName ? CRM_Utils_Array
::value($activityTypeName, $activityInstances) : $activityInstances;
766 * @return array|mixed
768 public function getCaseManagerRoleId($caseType) {
769 $xml = $this->retrieve($caseType);
770 return $this->caseRoles($xml->CaseRoles
, TRUE);
774 * @param string $caseType
776 * @return array<\Civi\CCase\CaseChangeListener>
778 public function getListeners($caseType) {
779 $xml = $this->retrieve($caseType);
780 $listeners = array();
781 if ($xml->Listeners
&& $xml->Listeners
->Listener
) {
782 foreach ($xml->Listeners
->Listener
as $listenerXML) {
783 $class = (string) $listenerXML;
784 $listeners[] = new $class();
793 public function getRedactActivityEmail() {
794 return $this->getBoolSetting('civicaseRedactActivityEmail', 'RedactActivityEmail');
798 * Retrieves AllowMultipleCaseClients setting.
801 * 1 if allowed, 0 if not
803 public function getAllowMultipleCaseClients() {
804 return $this->getBoolSetting('civicaseAllowMultipleClients', 'AllowMultipleCaseClients');
808 * Retrieves NaturalActivityTypeSort setting.
811 * 1 if natural, 0 if alphabetic
813 public function getNaturalActivityTypeSort() {
814 return $this->getBoolSetting('civicaseNaturalActivityTypeSort', 'NaturalActivityTypeSort');
818 * @param string $settingKey
819 * @param string $xmlTag
820 * @param mixed $default
824 private function getBoolSetting($settingKey, $xmlTag, $default = 0) {
825 $setting = Civi
::settings()->get($settingKey);
826 if ($setting !== 'default') {
827 return (int) $setting;
829 if ($xml = $this->retrieve("Settings")) {
830 return (string) $xml->{$xmlTag} ?
1 : 0;