mirror of
https://github.com/unraid/webgui.git
synced 2026-05-06 20:30:50 -05:00
VMs: preserve XML custom settings in VM conifg updates
This commit is contained in:
@@ -39,7 +39,6 @@ if (!empty($_GET['uuid'])) {
|
||||
}
|
||||
|
||||
$strIconURL = $lv->domain_get_icon_url($res);
|
||||
|
||||
$arrLoad = [
|
||||
'name' => $lv->domain_get_name($res),
|
||||
'icon' => basename($strIconURL),
|
||||
@@ -49,14 +48,12 @@ if (!empty($_GET['uuid'])) {
|
||||
];
|
||||
|
||||
if (empty($_GET['template'])) {
|
||||
// read vm-template attribute
|
||||
$strTemplateOS = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@os');
|
||||
if (empty($strTemplateOS)) {
|
||||
$strTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
|
||||
if (!empty($strTemplate)) {
|
||||
$strSelectedTemplate = $strTemplate;
|
||||
}
|
||||
if ($strTemplateOS) {
|
||||
$strSelectedTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
|
||||
} else {
|
||||
// Legacy VM support for <6.2 but need it going forward too
|
||||
// legacy VM support for <6.2 but need it going forward too
|
||||
foreach ($arrAllTemplates as $strName => $arrTemplate) {
|
||||
if (!empty($arrTemplate) && !empty($arrTemplate['os']) && $arrTemplate['os'] == $strTemplateOS) {
|
||||
$strSelectedTemplate = $strName;
|
||||
@@ -65,12 +62,11 @@ if (!empty($_GET['uuid'])) {
|
||||
}
|
||||
}
|
||||
if (empty($strSelectedTemplate) || empty($arrAllTemplates[$strSelectedTemplate])) {
|
||||
$strSelectedTemplate = 'Custom';
|
||||
$strSelectedTemplate = 'Windows 10'; //default to Windows 10
|
||||
}
|
||||
}
|
||||
$arrLoad['form'] = $arrAllTemplates[$strSelectedTemplate]['form'];
|
||||
}
|
||||
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2017, Lime Technology
|
||||
* Copyright 2015-2017, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
/* Copyright 2005-2018, Lime Technology
|
||||
* Copyright 2015-2018, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2018, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
@@ -13,6 +14,7 @@
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/webGui/include/Custom.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
$arrValidMachineTypes = getValidMachineTypes();
|
||||
@@ -93,98 +95,54 @@
|
||||
'target' => ''
|
||||
]
|
||||
]
|
||||
];
|
||||
];
|
||||
|
||||
// Merge in any default values from the VM template
|
||||
if (!empty($arrAllTemplates[$strSelectedTemplate]) && !empty($arrAllTemplates[$strSelectedTemplate]['overrides'])) {
|
||||
if ($arrAllTemplates[$strSelectedTemplate] && $arrAllTemplates[$strSelectedTemplate]['overrides']) {
|
||||
$arrConfigDefaults = array_replace_recursive($arrConfigDefaults, $arrAllTemplates[$strSelectedTemplate]['overrides']);
|
||||
}
|
||||
|
||||
if (array_key_exists('updatevm', $_POST) && !empty($_POST['domain']['uuid'])) {
|
||||
$_GET['uuid'] = $_POST['domain']['uuid'];
|
||||
}
|
||||
|
||||
// If we are editing a existing VM load it's existing configuration details
|
||||
$boolNew = true;
|
||||
$boolRunning = false;
|
||||
$arrExistingConfig = [];
|
||||
$strUUID = '';
|
||||
$strXML = '';
|
||||
if (!empty($_GET['uuid'])) {
|
||||
$strUUID = $_GET['uuid'];
|
||||
$res = $lv->domain_get_name_by_uuid($strUUID);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
|
||||
$boolNew = false;
|
||||
$boolRunning = ($lv->domain_state_translate($dom['state']) != 'shutoff');
|
||||
$arrExistingConfig = domain_to_config($strUUID);
|
||||
$strXML = $lv->domain_get_xml($res);
|
||||
}
|
||||
|
||||
// Active config for this page
|
||||
$arrConfig = array_replace_recursive($arrConfigDefaults, $arrExistingConfig);
|
||||
|
||||
// Add any custom metadata field defaults (e.g. os)
|
||||
if (empty($arrConfig['template']['os'])) {
|
||||
$arrConfig['template']['os'] = ($arrConfig['domain']['clock'] == 'localtime' ? 'windows' : 'linux');
|
||||
}
|
||||
|
||||
if (array_key_exists('createvm', $_POST)) {
|
||||
$arrResponse = ['success' => true];
|
||||
|
||||
if (array_key_exists('xmldesc', $_POST)) {
|
||||
$tmp = $lv->domain_define($_POST['xmldesc'], !empty($config['domain']['xmlstartnow']));
|
||||
if (!$tmp){
|
||||
$arrResponse = ['error' => $lv->get_last_error()];
|
||||
} else {
|
||||
$lv->domain_set_autostart($tmp, $_POST['domain']['autostart'] == 1);
|
||||
// create new VM
|
||||
if ($_POST['createvm']) {
|
||||
if ($lv->domain_new($_POST)){
|
||||
// Fire off the vnc popup if available
|
||||
$dom = $lv->get_domain_by_name($_POST['domain']['name']);
|
||||
$vncport = $lv->domain_get_vnc_port($dom);
|
||||
$wsport = $lv->domain_get_ws_port($dom);
|
||||
if ($vncport > 0) {
|
||||
$vnc = '/plugins/dynamix.vm.manager/vnc.html?autoconnect=true&host='.$_SERVER['HTTP_HOST'].'&port='.$wsport.'&path=';
|
||||
$reply['vncurl'] = $vnc;
|
||||
}
|
||||
$reply = ['success' => true];
|
||||
} else {
|
||||
$tmp = $lv->domain_new($_POST);
|
||||
if (!$tmp){
|
||||
$arrResponse = ['error' => $lv->get_last_error()];
|
||||
} else {
|
||||
// Fire off the vnc popup if available
|
||||
$dom = $lv->get_domain_by_name($_POST['domain']['name']);
|
||||
$vncport = $lv->domain_get_vnc_port($dom);
|
||||
$wsport = $lv->domain_get_ws_port($dom);
|
||||
|
||||
if ($vncport > 0) {
|
||||
$vnc = '/plugins/dynamix.vm.manager/vnc.html?autoconnect=true&host=' . $_SERVER['HTTP_HOST'] . '&port=' . $wsport . '&path=';
|
||||
$arrResponse['vncurl'] = $vnc;
|
||||
}
|
||||
}
|
||||
$reply = ['error' => $lv->get_last_error()];
|
||||
}
|
||||
|
||||
echo json_encode($arrResponse);
|
||||
echo json_encode($reply);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (array_key_exists('updatevm', $_POST)) {
|
||||
$dom = $lv->domain_get_domain_by_uuid($_POST['domain']['uuid']);
|
||||
// update existing VM
|
||||
if ($_POST['updatevm']) {
|
||||
$uuid = $_POST['domain']['uuid'];
|
||||
$dom = $lv->domain_get_domain_by_uuid($uuid);
|
||||
$oldAutoStart = $lv->domain_get_autostart($dom)==1;
|
||||
$newAutoStart = $_POST['domain']['autostart']==1;
|
||||
$strXML = $lv->domain_get_xml($dom);
|
||||
|
||||
if ($boolRunning) {
|
||||
if ($lv->domain_get_state($dom)=='running') {
|
||||
$arrErrors = [];
|
||||
|
||||
$arrExistingConfig = domain_to_config($_POST['domain']['uuid']);
|
||||
$arrExistingConfig = domain_to_config($uuid);
|
||||
$arrNewUSBIDs = $_POST['usb'];
|
||||
|
||||
// hot-attach any new usb devices
|
||||
foreach ($arrNewUSBIDs as $strNewUSBID) {
|
||||
foreach ($arrExistingConfig['usb'] as $arrExistingUSB) {
|
||||
if ($strNewUSBID == $arrExistingUSB['id']) {
|
||||
continue 2;
|
||||
}
|
||||
if ($strNewUSBID == $arrExistingUSB['id']) continue 2;
|
||||
}
|
||||
list($strVendor, $strProduct) = explode(':', $strNewUSBID);
|
||||
list($strVendor,$strProduct) = explode(':', $strNewUSBID);
|
||||
// hot-attach usb
|
||||
file_put_contents('/tmp/hotattach.tmp', "<hostdev mode='subsystem' type='usb'>
|
||||
<source startupPolicy='optional'>
|
||||
<vendor id='0x".$strVendor."'/>
|
||||
<product id='0x".$strProduct."'/>
|
||||
</source>
|
||||
</hostdev>");
|
||||
exec("virsh attach-device " . escapeshellarg($_POST['domain']['uuid']) . " /tmp/hotattach.tmp --live 2>&1", $arrOutput, $intReturnCode);
|
||||
file_put_contents('/tmp/hotattach.tmp', "<hostdev mode='subsystem' type='usb'><source startupPolicy='optional'><vendor id='0x".$strVendor."'/><product id='0x".$strProduct."'/></source></hostdev>");
|
||||
exec("virsh attach-device ".escapeshellarg($uuid)." /tmp/hotattach.tmp --live 2>&1", $arrOutput, $intReturnCode);
|
||||
if ($intReturnCode != 0) {
|
||||
$arrErrors[] = implode(' ', $arrOutput);
|
||||
}
|
||||
@@ -194,89 +152,78 @@
|
||||
foreach ($arrExistingConfig['usb'] as $arrExistingUSB) {
|
||||
if (!in_array($arrExistingUSB['id'], $arrNewUSBIDs)) {
|
||||
list($strVendor, $strProduct) = explode(':', $arrExistingUSB['id']);
|
||||
|
||||
file_put_contents('/tmp/hotdetach.tmp', "<hostdev mode='subsystem' type='usb'>
|
||||
<source startupPolicy='optional'>
|
||||
<vendor id='0x".$strVendor."'/>
|
||||
<product id='0x".$strProduct."'/>
|
||||
</source>
|
||||
</hostdev>");
|
||||
exec("virsh detach-device " . escapeshellarg($_POST['domain']['uuid']) . " /tmp/hotdetach.tmp --live 2>&1", $arrOutput, $intReturnCode);
|
||||
if ($intReturnCode != 0) {
|
||||
$arrErrors[] = implode(' ', $arrOutput);
|
||||
}
|
||||
file_put_contents('/tmp/hotdetach.tmp', "<hostdev mode='subsystem' type='usb'><source startupPolicy='optional'><vendor id='0x".$strVendor."'/><product id='0x".$strProduct."'/></source></hostdev>");
|
||||
exec("virsh detach-device ".escapeshellarg($uuid)." /tmp/hotdetach.tmp --live 2>&1", $arrOutput, $intReturnCode);
|
||||
if ($intReturnCode != 0) $arrErrors[] = implode(' ',$arrOutput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (empty($arrErrors)) {
|
||||
$arrResponse = ['success' => true];
|
||||
} else {
|
||||
$arrResponse = ['error' => implode(', ', $arrErrors)];
|
||||
}
|
||||
echo json_encode($arrResponse);
|
||||
$reply = !$arrErrors ? ['success' => true] : ['error' => implode(', ',$arrErrors)];
|
||||
echo json_encode($reply);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Backup xml for existing domain in ram
|
||||
$strOldXML = '';
|
||||
$boolOldAutoStart = false;
|
||||
// backup xml for existing domain in ram
|
||||
if ($dom) {
|
||||
$strOldXML = $lv->domain_get_xml($dom);
|
||||
$boolOldAutoStart = $lv->domain_get_autostart($dom);
|
||||
if (!array_key_exists('xmldesc', $_POST)) {
|
||||
$strOldName = $lv->domain_get_name($dom);
|
||||
$strNewName = $_POST['domain']['name'];
|
||||
|
||||
if (!empty($strOldName) &&
|
||||
!empty($strNewName) &&
|
||||
is_dir($domain_cfg['DOMAINDIR'].$strOldName.'/') &&
|
||||
!is_dir($domain_cfg['DOMAINDIR'].$strNewName.'/')) {
|
||||
|
||||
// mv domain/vmname folder
|
||||
if (rename($domain_cfg['DOMAINDIR'].$strOldName, $domain_cfg['DOMAINDIR'].$strNewName)) {
|
||||
// replace all disk paths in xml
|
||||
foreach ($_POST['disk'] as &$arrDisk) {
|
||||
if (!empty($arrDisk['new'])) {
|
||||
$arrDisk['new'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['new']);
|
||||
}
|
||||
if (!empty($arrDisk['image'])) {
|
||||
$arrDisk['image'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['image']);
|
||||
}
|
||||
}
|
||||
$oldName = $lv->domain_get_name($dom);
|
||||
$newName = $_POST['domain']['name'];
|
||||
$oldDir = $domain_cfg['DOMAINDIR'].$oldName;
|
||||
$newDir = $domain_cfg['DOMAINDIR'].$newdName;
|
||||
if ($oldName && $newName && is_dir($oldDir) && !is_dir($newDir)) {
|
||||
// mv domain/vmname folder
|
||||
if (rename($oldDir, $newDir)) {
|
||||
// replace all disk paths in xml
|
||||
foreach ($_POST['disk'] as &$arrDisk) {
|
||||
if ($arrDisk['new']) $arrDisk['new'] = str_replace($oldDir, $newDir, $arrDisk['new']);
|
||||
if ($arrDisk['image']) $arrDisk['image'] = str_replace($oldDir, $newDir, $arrDisk['image']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove existing domain
|
||||
$lv->nvram_backup($_POST['domain']['uuid']);
|
||||
// construct updated config
|
||||
$arrExistingConfig = custom::createArray('domain',$strXML);
|
||||
$arrExistingConfig['metadata']['vmtemplate']['@attributes']['xmlns'] = 'unraid';
|
||||
$arrExistingConfig['cputune']['vcpupin'] = [];
|
||||
$arrUpdatedConfig = custom::createArray('domain',$lv->config_to_xml($_POST));
|
||||
$arrConfig = array_replace_recursive($arrExistingConfig, $arrUpdatedConfig);
|
||||
$xml = custom::createXML('domain',$arrConfig)->saveXML();
|
||||
|
||||
// delete and create the VM
|
||||
$lv->nvram_backup($uuid);
|
||||
$lv->domain_undefine($dom);
|
||||
$lv->nvram_restore($_POST['domain']['uuid']);
|
||||
|
||||
// Save new domain
|
||||
if (array_key_exists('xmldesc', $_POST)) {
|
||||
$tmp = $lv->domain_define($_POST['xmldesc']);
|
||||
$lv->nvram_restore($uuid);
|
||||
$new = $lv->domain_define($xml);
|
||||
if ($new) {
|
||||
$lv->domain_set_autostart($new, $newAutoStart);
|
||||
$reply = ['success' => true];
|
||||
} else {
|
||||
$tmp = $lv->domain_new($_POST);
|
||||
}
|
||||
if (!$tmp){
|
||||
$strLastError = $lv->get_last_error();
|
||||
|
||||
// Failure -- try to restore existing domain
|
||||
$tmp = $lv->domain_define($strOldXML);
|
||||
if ($tmp) $lv->domain_set_autostart($tmp, $boolOldAutoStart);
|
||||
|
||||
$arrResponse = ['error' => $strLastError];
|
||||
} else {
|
||||
$lv->domain_set_autostart($tmp, $_POST['domain']['autostart'] == 1);
|
||||
|
||||
$arrResponse = ['success' => true];
|
||||
$old = $lv->domain_define($strXML);
|
||||
if ($old) $lv->domain_set_autostart($old, $oldAutoStart);
|
||||
$reply = ['error' => $lv->get_last_error()];
|
||||
}
|
||||
|
||||
echo json_encode($arrResponse);
|
||||
echo json_encode($reply);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_GET['uuid']) {
|
||||
// edit an existing VM
|
||||
$dom = $lv->domain_get_domain_by_uuid($_GET['uuid']);
|
||||
$boolRunning = $lv->domain_get_state($dom)=='running';
|
||||
$strXML = $lv->domain_get_xml($dom);
|
||||
$boolNew = false;
|
||||
$arrConfig = domain_to_config($_GET['uuid']);
|
||||
} else {
|
||||
// edit new VM
|
||||
$boolRunning = false;
|
||||
$strXML = '';
|
||||
$boolNew = true;
|
||||
$arrConfig = $arrConfigDefaults;
|
||||
}
|
||||
// Add any custom metadata field defaults (e.g. os)
|
||||
if (!$arrConfig['template']['os']) {
|
||||
$arrConfig['template']['os'] = ($arrConfig['domain']['clock']=='localtime' ? 'windows' : 'linux');
|
||||
}
|
||||
?>
|
||||
|
||||
<link rel="stylesheet" href="/plugins/dynamix.vm.manager/scripts/codemirror/lib/codemirror.css">
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2018, Lime Technology
|
||||
* Copyright 2012-2018, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* Modified for unRAID of Array2XML Published February 9, 2016 by Jeetendra Singh
|
||||
* Modified for unRAID of xmlToArray Published August 23, 2012 by Tamlyn Rhodes
|
||||
*/
|
||||
class custom {
|
||||
private static $xml = null;
|
||||
private static $encoding = 'UTF-8';
|
||||
/*
|
||||
* Initialize the root XML node [optional]
|
||||
* @param $version
|
||||
* @param $encoding
|
||||
* @param $format_output
|
||||
*/
|
||||
public static function init($version = '1.0', $encoding = 'UTF-8', $format_output = true) {
|
||||
self::$xml = new DomDocument($version, $encoding);
|
||||
self::$xml->formatOutput = $format_output;
|
||||
self::$encoding = $encoding;
|
||||
}
|
||||
/*
|
||||
* Convert an Array to XML
|
||||
* @param string $node_name - name of the root node to be converted
|
||||
* @param array $arr - aray to be converterd
|
||||
* @return DomDocument
|
||||
*/
|
||||
public static function &createXML($node_name, $arr=array()) {
|
||||
$xml = self::getXMLRoot();
|
||||
$xml->appendChild(self::convert($node_name, $arr));
|
||||
self::$xml = null; // clear the xml node in the class for 2nd time use.
|
||||
return $xml;
|
||||
}
|
||||
/*
|
||||
* Convert an Array to XML
|
||||
* @param string $node_name - name of the root node to be converted
|
||||
* @param array $arr - aray to be converterd
|
||||
* @return DOMNode
|
||||
*/
|
||||
private static function &convert($node_name, $arr=array()) {
|
||||
//print_arr($node_name);
|
||||
$xml = self::getXMLRoot();
|
||||
$node = $xml->createElement($node_name);
|
||||
if (is_array($arr)) {
|
||||
// get the attributes first.;
|
||||
if (isset($arr['@attributes'])) {
|
||||
foreach ($arr['@attributes'] as $key => $value) {
|
||||
if (!self::isValidTagName($key)) {
|
||||
throw new Exception("[custom] Illegal character in attribute name. Attribute: $key in node: $node_name");
|
||||
}
|
||||
$node->setAttribute($key, self::bool2str($value));
|
||||
}
|
||||
unset($arr['@attributes']); //remove the key from the array once done.
|
||||
}
|
||||
// check if it has a value stored in @value, if yes store the value and return
|
||||
// else check if its directly stored as string
|
||||
if (isset($arr['@value'])) {
|
||||
$node->appendChild($xml->createTextNode(self::bool2str($arr['@value'])));
|
||||
unset($arr['@value']); //remove the key from the array once done.
|
||||
//return from recursion, as a note with value cannot have child nodes.
|
||||
return $node;
|
||||
} elseif (isset($arr['@cdata'])) {
|
||||
$node->appendChild($xml->createCDATASection(self::bool2str($arr['@cdata'])));
|
||||
unset($arr['@cdata']); //remove the key from the array once done.
|
||||
//return from recursion, as a note with cdata cannot have child nodes.
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
//create subnodes using recursion
|
||||
if (is_array($arr)) {
|
||||
// recurse to get the node for that key
|
||||
foreach ($arr as $key=>$value) {
|
||||
if (!self::isValidTagName($key)) {
|
||||
throw new Exception("[custom] Illegal character in tag name. Tag: $key in node: $node_name");
|
||||
}
|
||||
if (is_array($value) && is_numeric(key($value))) {
|
||||
// MORE THAN ONE NODE OF ITS KIND;
|
||||
// if the new array is numeric index, means it is array of nodes of the same kind
|
||||
// it should follow the parent key name
|
||||
foreach ($value as $k=>$v) {
|
||||
$node->appendChild(self::convert($key, $v));
|
||||
}
|
||||
} else {
|
||||
// ONLY ONE NODE OF ITS KIND
|
||||
$node->appendChild(self::convert($key, $value));
|
||||
}
|
||||
unset($arr[$key]); //remove the key from the array once done.
|
||||
}
|
||||
}
|
||||
// after we are done with all the keys in the array (if it is one)
|
||||
// we check if it has any text value, if yes, append it.
|
||||
if (!is_array($arr)) {
|
||||
$node->appendChild($xml->createTextNode(self::bool2str($arr)));
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
/*
|
||||
* Get the root XML node, if there isn't one, create it.
|
||||
*/
|
||||
private static function getXMLRoot() {
|
||||
if (empty(self::$xml)) {
|
||||
self::init();
|
||||
}
|
||||
return self::$xml;
|
||||
}
|
||||
/*
|
||||
* Get string representation of boolean value
|
||||
*/
|
||||
private static function bool2str($v) {
|
||||
//convert boolean to text value.
|
||||
$v = $v === true ? 'true' : $v;
|
||||
$v = $v === false ? 'false' : $v;
|
||||
return $v;
|
||||
}
|
||||
/*
|
||||
* Check if the tag name or attribute name contains illegal characters
|
||||
* Ref: http://www.w3.org/TR/xml/#sec-common-syn
|
||||
*/
|
||||
private static function isValidTagName($tag) {
|
||||
$pattern = '/^[a-z_]+[a-z0-9\:\-\.\_]*[^:]*$/i';
|
||||
return preg_match($pattern, $tag, $matches) && $matches[0] == $tag;
|
||||
}
|
||||
/*
|
||||
* Convert xml string into array.
|
||||
*/
|
||||
public static function &createArray($root, $xmlstring) {
|
||||
$xml = simplexml_load_string($xmlstring);
|
||||
return self::XML2Array($xml)[$root];
|
||||
}
|
||||
/*
|
||||
* Custom converter to process both values and attributes in XML nodes
|
||||
*/
|
||||
private static function &XML2Array($xml) {
|
||||
$attributes = [];
|
||||
foreach ($xml->attributes() as $attributeName => $attribute) {
|
||||
$attributes['@attributes'][$attributeName] = (string)$attribute;
|
||||
}
|
||||
$tags = [];
|
||||
foreach ($xml->children() as $child) {
|
||||
$array = self::XML2Array($child);
|
||||
list($node, $data) = each($array);
|
||||
if (!isset($tags[$node])) {
|
||||
$tags[$node] = $data;
|
||||
} elseif (is_array($tags[$node]) && array_keys($tags[$node])===range(0, count($tags[$node])-1)) {
|
||||
$tags[$node][] = $data;
|
||||
} else {
|
||||
$tags[$node] = array($tags[$node], $data);
|
||||
}
|
||||
}
|
||||
$textContent = [];
|
||||
$plainText = trim((string)$xml);
|
||||
if ($plainText !== '') $textContent['@value'] = $plainText;
|
||||
$properties = $attributes || $tags || ($plainText==='') ? array_merge($attributes, $tags, $textContent) : $plainText;
|
||||
return array($xml->getName() => $properties);
|
||||
}
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user