3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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-2019
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 [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 [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);
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),
112 CRM_Core_Error
::fatal();
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);
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,
134 elseif ($activitySetName) {
135 $name = (string ) $activitySetXML->name
;
136 if ($name == $activitySetName) {
137 return $this->processActivitySet($activitySetXML,
147 * @param $activitySetXML
148 * @param array $params
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)
154 // delete all existing activities which are non-empty
155 $this->deleteEmptyActivity($params);
158 foreach ($activitySetXML->ActivityTypes
as $activityTypesXML) {
159 foreach ($activityTypesXML as $activityTypeXML) {
160 $this->createActivity($activityTypeXML, $params);
166 * @param $activitySetXML
167 * @param array $params
169 public function processActivitySet($activitySetXML, &$params) {
170 foreach ($activitySetXML->ActivityTypes
as $activityTypesXML) {
171 foreach ($activityTypesXML as $activityTypeXML) {
172 $this->createActivity($activityTypeXML, $params);
178 * @param $caseRolesXML
179 * @param bool $isCaseManager
181 * @return array|mixed
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);
191 foreach ($caseRolesXML as $caseRoleXML) {
192 foreach ($caseRoleXML->RelationshipType
as $relationshipTypeXML) {
193 $relationshipTypeName = (string ) $relationshipTypeXML->name
;
194 $relationshipTypeID = array_search($relationshipTypeName,
197 if ($relationshipTypeID === FALSE) {
201 if (!$isCaseManager) {
202 $result[$relationshipTypeID] = $relationshipTypesToReturn[$relationshipTypeID];
204 elseif ($relationshipTypeXML->manager
== 1) {
205 return $relationshipTypeID;
213 * @param string $relationshipTypeName
214 * @param array $params
219 public function createRelationships($relationshipTypeName, &$params) {
220 // The relationshipTypeName is coming from XML, so the argument should be
222 $relationshipTypes = &$this->allRelationshipTypes(TRUE);
223 // get the relationship
224 $relationshipType = array_search($relationshipTypeName, $relationshipTypes);
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]
234 $client = $params['clientID'];
235 if (!is_array($client)) {
239 foreach ($client as $key => $clientId) {
240 $relationshipParams = [
241 'relationship_type_id' => substr($relationshipType, 0, -4),
243 'case_id' => $params['caseID'],
244 'start_date' => date("Ymd"),
245 'end_date' => CRM_Utils_Array
::value('relationship_end_date', $params),
248 if (substr($relationshipType, -4) == '_b_a') {
249 $relationshipParams['contact_id_b'] = $clientId;
250 $relationshipParams['contact_id_a'] = $params['creatorID'];
252 if (substr($relationshipType, -4) == '_a_b') {
253 $relationshipParams['contact_id_a'] = $clientId;
254 $relationshipParams['contact_id_b'] = $params['creatorID'];
257 if (!$this->createRelationship($relationshipParams)) {
258 CRM_Core_Error
::fatal();
266 * @param array $params
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)) {
281 * @param $activityTypesXML
282 * @param bool $maxInst
283 * @param bool $isLabel
284 * @param bool $maskAction
288 public function activityTypes($activityTypesXML, $maxInst = FALSE, $isLabel = FALSE, $maskAction = FALSE) {
289 $activityTypes = &$this->allActivityTypes(TRUE, TRUE);
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);
297 if ($activityTypeInfo['id']) {
299 if ($maskAction == 'edit' && '0' === (string ) $recordXML->editable
) {
300 $result[$maskAction][] = $activityTypeInfo['id'];
305 //if we want,labels of activities should be returned.
307 $result[$activityTypeInfo['id']] = $activityTypeInfo['label'];
310 $result[$activityTypeInfo['id']] = $activityTypeName;
315 $result[$activityTypeName] = $maxInstances;
323 // call option value hook
324 CRM_Utils_Hook
::optionValues($result, 'case_activity_type');
330 * @param SimpleXMLElement $caseTypeXML
332 * @return array<string> symbolic activity-type names
334 public function getDeclaredActivityTypes($caseTypeXML) {
337 if (!empty($caseTypeXML->ActivityTypes
) && $caseTypeXML->ActivityTypes
->ActivityType
) {
338 foreach ($caseTypeXML->ActivityTypes
->ActivityType
as $activityTypeXML) {
339 $result[] = (string) $activityTypeXML->name
;
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
;
353 $result = array_unique($result);
359 * Relationships are straight from XML, described from perspective of non-client
361 * @param SimpleXMLElement $caseTypeXML
363 * @return array<string> symbolic relationship-type names
365 public function getDeclaredRelationshipTypes($caseTypeXML) {
368 if (!empty($caseTypeXML->CaseRoles
) && $caseTypeXML->CaseRoles
->RelationshipType
) {
369 foreach ($caseTypeXML->CaseRoles
->RelationshipType
as $relTypeXML) {
370 $result[] = (string) $relTypeXML->name
;
374 $result = array_unique($result);
380 * @param array $params
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);
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
394 AND a.is_current_revision = 1
397 $sqlParams = [1 => [$params['clientID'], 'Integer'], 2 => [$params['caseID'], 'Integer']];
398 CRM_Core_DAO
::executeQuery($query, $sqlParams);
402 * @param array $params
406 public function isActivityPresent(&$params) {
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
417 1 => [$params['activityTypeID'], 'Integer'],
418 2 => [$params['caseID'], 'Integer'],
420 $count = CRM_Core_DAO
::singleValueQuery($query, $sqlParams);
422 // check for max instance
423 $caseType = CRM_Case_BAO_Case
::getCaseType($params['caseID'], 'name');
424 $maxInstance = self
::getMaxInstance($caseType, $params['activityTypeName']);
426 return $maxInstance ?
($count < $maxInstance ?
FALSE : TRUE) : FALSE;
430 * @param $activityTypeXML
431 * @param array $params
434 * @throws CRM_Core_Exception
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);
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]
450 $activityTypeID = $activityTypeInfo['id'];
452 if (isset($activityTypeXML->status
)) {
453 $statusName = (string) $activityTypeXML->status
;
456 $statusName = 'Scheduled';
459 $client = (array) $params['clientID'];
463 if (isset($activityTypeXML->order
)) {
464 $orderVal = (string) $activityTypeXML->order
;
467 if ($activityTypeName == 'Open Case') {
469 'activity_type_id' => $activityTypeID,
470 'source_contact_id' => $params['creatorID'],
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,
485 'activity_type_id' => $activityTypeID,
486 'source_contact_id' => $params['creatorID'],
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,
495 $activityParams['assignee_contact_id'] = $this->getDefaultAssigneeForActivity($activityParams, $activityTypeXML);
497 //parsing date to default preference format
498 $params['activity_date_time'] = CRM_Utils_Date
::processDate($params['activity_date_time']);
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'];
508 // Add parameters for attachments
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];
519 $activityDate = NULL;
520 //get date of reference activity if set.
521 if ($referenceActivityName = (string) $activityTypeXML->reference_activity
) {
523 //we skip open case as reference activity.CRM-4374.
524 if (!empty($params['resetTimeline']) && $referenceActivityName == 'Open Case') {
525 $activityDate = $params['activity_date_time'];
528 $referenceActivityInfo = CRM_Utils_Array
::value($referenceActivityName, $activityTypes);
529 if ($referenceActivityInfo['id']) {
530 $caseActivityParams = ['activity_type_id' => $referenceActivityInfo['id']];
532 //if reference_select is set take according activity.
533 if ($referenceSelect = (string) $activityTypeXML->reference_select
) {
534 $caseActivityParams[$referenceSelect] = 1;
537 $referenceActivity = CRM_Case_BAO_Case
::getCaseActivityDates($params['caseID'], $caseActivityParams, TRUE);
539 if (is_array($referenceActivity)) {
540 foreach ($referenceActivity as $aId => $details) {
541 $activityDate = CRM_Utils_Array
::value('activity_date', $details);
548 if (!$activityDate) {
549 $activityDate = $params['activity_date_time'];
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
,
560 $activityParams['activity_date_time'] = CRM_Utils_Date
::format($activityDateTime);
563 // if same activity is already there, skip and dont touch
564 $params['activityTypeID'] = $activityTypeID;
565 $params['activityTypeName'] = $activityTypeName;
566 if ($this->isActivityPresent($params)) {
569 $activityParams['case_id'] = $params['caseID'];
570 if (!empty($activityParams['is_auto'])) {
571 $activityParams['skipRecentView'] = TRUE;
574 // @todo - switch to using api & remove the parameter pre-wrangling above.
575 $activity = CRM_Activity_BAO_Activity
::create($activityParams);
578 CRM_Core_Error
::fatal();
582 // create case activity record
584 'activity_id' => $activity->id
,
585 'case_id' => $params['caseID'],
587 CRM_Case_BAO_Case
::processCaseActivity($caseParams);
592 * Return the default assignee contact for the activity.
594 * @param array $activityParams
595 * @param object $activityTypeXML
597 * @return int|null the ID of the default assignee contact or null if none.
599 protected function getDefaultAssigneeForActivity($activityParams, $activityTypeXML) {
600 if (!isset($activityTypeXML->default_assignee_type
)) {
604 $defaultAssigneeOptionsValues = $this->getDefaultAssigneeOptionValues();
606 switch ($activityTypeXML->default_assignee_type
) {
607 case $defaultAssigneeOptionsValues['BY_RELATIONSHIP']:
608 return $this->getDefaultAssigneeByRelationship($activityParams, $activityTypeXML);
611 case $defaultAssigneeOptionsValues['SPECIFIC_CONTACT']:
612 return $this->getDefaultAssigneeBySpecificContact($activityTypeXML);
615 case $defaultAssigneeOptionsValues['USER_CREATING_THE_CASE']:
616 return $activityParams['source_contact_id'];
619 case $defaultAssigneeOptionsValues['NONE']:
626 * Fetches and caches the activity's default assignee options.
630 protected function getDefaultAssigneeOptionValues() {
631 if (!empty($this->defaultAssigneeOptionsValues
)) {
632 return $this->defaultAssigneeOptionsValues
;
635 $defaultAssigneeOptions = civicrm_api3('OptionValue', 'get', [
636 'option_group_id' => 'activity_default_assignee',
637 'options' => ['limit' => 0],
640 foreach ($defaultAssigneeOptions['values'] as $option) {
641 $this->defaultAssigneeOptionsValues
[$option['name']] = $option['value'];
644 return $this->defaultAssigneeOptionsValues
;
648 * Returns the default assignee for the activity by searching for the target's
649 * contact relationship type defined in the activity's details.
651 * @param array $activityParams
652 * @param object $activityTypeXML
654 * @return int|null the ID of the default assignee contact or null if none.
656 protected function getDefaultAssigneeByRelationship($activityParams, $activityTypeXML) {
657 $isDefaultRelationshipDefined = isset($activityTypeXML->default_assignee_relationship
)
658 && preg_match('/\d+_[ab]_[ab]/', $activityTypeXML->default_assignee_relationship
);
660 if (!$isDefaultRelationshipDefined) {
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
);
670 'relationship_type_id' => $relTypeId,
671 "contact_id_$b" => $targetContactId,
675 if ($this->isBidirectionalRelationshipType($relTypeId)) {
676 $params["contact_id_$a"] = $targetContactId;
677 $params['options']['or'] = [['contact_id_a', 'contact_id_b']];
680 $relationships = civicrm_api3('Relationship', 'get', $params);
682 if ($relationships['count']) {
683 $relationship = CRM_Utils_Array
::first($relationships['values']);
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'];
696 * Determines if the given relationship type is bidirectional or not by
697 * comparing their labels.
701 protected function isBidirectionalRelationshipType($relationshipTypeId) {
702 $relationshipTypeResult = civicrm_api3('RelationshipType', 'get', [
703 'id' => $relationshipTypeId,
704 'options' => ['limit' => 1],
707 if ($relationshipTypeResult['count'] === 0) {
711 $relationshipType = CRM_Utils_Array
::first($relationshipTypeResult['values']);
713 return $relationshipType['label_b_a'] === $relationshipType['label_a_b'];
717 * Returns the activity's default assignee for a specific contact if the contact exists,
718 * otherwise returns null.
720 * @param object $activityTypeXML
724 protected function getDefaultAssigneeBySpecificContact($activityTypeXML) {
725 if (!$activityTypeXML->default_assignee_contact
) {
729 $contact = civicrm_api3('Contact', 'get', [
730 'id' => $activityTypeXML->default_assignee_contact
,
733 if ($contact['count'] == 1) {
734 return $activityTypeXML->default_assignee_contact
;
741 * @param $activitySetsXML
745 public static function activitySets($activitySetsXML) {
747 foreach ($activitySetsXML as $activitySetXML) {
748 foreach ($activitySetXML as $recordXML) {
749 $activitySetName = (string ) $recordXML->name
;
750 $activitySetLabel = (string ) $recordXML->label
;
751 $result[$activitySetName] = $activitySetLabel;
760 * @param null $activityTypeName
762 * @return array|bool|mixed
765 public function getMaxInstance($caseType, $activityTypeName = NULL) {
766 $xml = $this->retrieve($caseType);
768 if ($xml === FALSE) {
769 CRM_Core_Error
::fatal();
773 $activityInstances = $this->activityTypes($xml->ActivityTypes
, TRUE);
774 return $activityTypeName ? CRM_Utils_Array
::value($activityTypeName, $activityInstances) : $activityInstances;
780 * @return array|mixed
782 public function getCaseManagerRoleId($caseType) {
783 $xml = $this->retrieve($caseType);
784 return $this->caseRoles($xml->CaseRoles
, TRUE);
788 * @param string $caseType
790 * @return array<\Civi\CCase\CaseChangeListener>
792 public function getListeners($caseType) {
793 $xml = $this->retrieve($caseType);
795 if ($xml->Listeners
&& $xml->Listeners
->Listener
) {
796 foreach ($xml->Listeners
->Listener
as $listenerXML) {
797 $class = (string) $listenerXML;
798 $listeners[] = new $class();
807 public function getRedactActivityEmail() {
808 return $this->getBoolSetting('civicaseRedactActivityEmail', 'RedactActivityEmail');
812 * Retrieves AllowMultipleCaseClients setting.
815 * 1 if allowed, 0 if not
817 public function getAllowMultipleCaseClients() {
818 return $this->getBoolSetting('civicaseAllowMultipleClients', 'AllowMultipleCaseClients');
822 * Retrieves NaturalActivityTypeSort setting.
825 * 1 if natural, 0 if alphabetic
827 public function getNaturalActivityTypeSort() {
828 return $this->getBoolSetting('civicaseNaturalActivityTypeSort', 'NaturalActivityTypeSort');
832 * @param string $settingKey
833 * @param string $xmlTag
834 * @param mixed $default
838 private function getBoolSetting($settingKey, $xmlTag, $default = 0) {
839 $setting = Civi
::settings()->get($settingKey);
840 if ($setting !== 'default') {
841 return (int) $setting;
843 if ($xml = $this->retrieve("Settings")) {
844 return (string) $xml->{$xmlTag} ?
1 : 0;
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.
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.
856 * @param SimpleXMLElement $xml
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:
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
870 if (isset($xml->machineName
)) {
871 $machineName = (string) $xml->machineName
;
872 if ($machineName !== '') {
876 return (string) $xml->name
;