mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 17:48:43 +02:00
N°4569 - Revert meta save of deleted nodes
This commit is contained in:
@@ -17,8 +17,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
use Combodo\iTop\DesignDocument;
|
||||
|
||||
|
||||
/**
|
||||
* Utility to upgrade the format of a given XML datamodel to the latest version
|
||||
@@ -111,8 +109,6 @@ class iTopDesignFormat
|
||||
*/
|
||||
protected $aLog;
|
||||
protected $bStatus;
|
||||
protected $bKeepObsoleteNodes;
|
||||
protected $sKeepVersion;
|
||||
|
||||
/**
|
||||
* Creation from a loaded DOMDocument
|
||||
@@ -214,20 +210,6 @@ class iTopDesignFormat
|
||||
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a real xpath from iTop one
|
||||
*
|
||||
* @param \Combodo\iTop\DesignElement $oNode
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function GetNodeXPath($oNode)
|
||||
{
|
||||
$sITopXPath = DesignDocument::GetItopNodePath($oNode);
|
||||
|
||||
return preg_replace(["@\[@", "@]@"], ["[@id=\"", "\"]"], $sITopXPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the conversion without altering the DOM
|
||||
*
|
||||
@@ -245,21 +227,19 @@ class iTopDesignFormat
|
||||
}
|
||||
|
||||
/**
|
||||
* Make adjustments to the DOM to migrate it to the specified version (default is latest)
|
||||
* Make adjustements to the DOM to migrate it to the specified version (default is latest)
|
||||
* For now only the conversion from version 1.0 to 1.1 is supported.
|
||||
*
|
||||
* @param string $sTargetVersion The desired version (or the latest possible version if not specified)
|
||||
* @param \ModelFactory|null $oFactory Full data model (not yet used, aimed at allowing conversion that could not be performed without knowing the
|
||||
* @param object $oFactory Full data model (not yet used, aimed at allowing conversion that could not be performed without knowing the
|
||||
* whole data model)
|
||||
* @param bool $bKeepObsoleteNodes
|
||||
*
|
||||
* @return bool True on success, False if errors have been encountered (still the DOM may be altered!)
|
||||
*/
|
||||
public function Convert($sTargetVersion = ITOP_DESIGN_LATEST_VERSION, $oFactory = null, $bKeepObsoleteNodes = true)
|
||||
public function Convert($sTargetVersion = ITOP_DESIGN_LATEST_VERSION, $oFactory = null)
|
||||
{
|
||||
$this->aLog = array();
|
||||
$this->bStatus = true;
|
||||
$this->bKeepObsoleteNodes = $bKeepObsoleteNodes;
|
||||
|
||||
$oXPath = new DOMXPath($this->oDocument);
|
||||
// Retrieve the version number
|
||||
@@ -322,7 +302,6 @@ class iTopDesignFormat
|
||||
$sIntermediate = self::$aVersions[$sFrom]['next'];
|
||||
$sTransform = self::$aVersions[$sFrom]['go_to_next'];
|
||||
$this->LogInfo("Upgrading from $sFrom to $sIntermediate ($sTransform)");
|
||||
$this->sKeepVersion = $sFrom;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -330,16 +309,12 @@ class iTopDesignFormat
|
||||
$sIntermediate = self::$aVersions[$sFrom]['previous'];
|
||||
$sTransform = self::$aVersions[$sFrom]['go_to_previous'];
|
||||
$this->LogInfo("Downgrading from $sFrom to $sIntermediate ($sTransform)");
|
||||
$this->sKeepVersion = null;
|
||||
}
|
||||
// Transform to the intermediate format
|
||||
$aCallSpec = array($this, $sTransform);
|
||||
try
|
||||
{
|
||||
call_user_func($aCallSpec, $oFactory);
|
||||
if ($iFrom > $iTo && $this->bKeepObsoleteNodes) {
|
||||
$this->RestorePreviousNodes($sIntermediate);
|
||||
}
|
||||
|
||||
// Recurse
|
||||
$this->DoConvert($sIntermediate, $sTo, $oFactory);
|
||||
@@ -988,117 +963,12 @@ class iTopDesignFormat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sNodeMetaVersion
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function RestorePreviousNodes($sNodeMetaVersion)
|
||||
{
|
||||
$oXPath = new DOMXPath($this->oDocument);
|
||||
$sVersion = str_replace('.', '_', $sNodeMetaVersion);
|
||||
$oTrashedNodes = $oXPath->query("/itop_design/meta/previous_versions/previous_version_$sVersion/trashed_nodes/trashed_node");
|
||||
foreach ($oTrashedNodes as $oTrashedNode) {
|
||||
if ($oTrashedNode->nodeType == XML_ELEMENT_NODE) {
|
||||
$oXPathNode = $oXPath->query('parent_xpath', $oTrashedNode)->item(0);
|
||||
$oNodeTreeNode = $oXPath->query('node_tree', $oTrashedNode)->item(0);
|
||||
if (!is_null($oXPathNode) && !is_null($oNodeTreeNode)) {
|
||||
$sXPath = $this->GetText($oXPathNode, '');
|
||||
$oParentNode = $this->GetOrBuildParentNode($sXPath);
|
||||
if ($oParentNode) {
|
||||
$oNode = $oNodeTreeNode->firstChild;
|
||||
while ($oNode) {
|
||||
$oNextNode = $oNode->nextSibling;
|
||||
if ($oNode->nodeType == XML_ELEMENT_NODE) {
|
||||
// Check for collision
|
||||
$sId = $oNode->getAttribute('id');
|
||||
$sNodeXPath = ($sId != '') ? $oNode->nodeName.'[@id="'.$sId.'"]' : $oNode->nodeName;
|
||||
$sNodeXPath = $sXPath.'/'.$sNodeXPath;
|
||||
$oTarget = $oXPath->query($sNodeXPath)->item(0);
|
||||
if ($oTarget) {
|
||||
// Do not continue migration
|
||||
throw new Exception("Trying to restore an existing node $sNodeXPath from version $sNodeMetaVersion");
|
||||
}
|
||||
// Restore the modification flags
|
||||
$oModifiedNodeList = $oXPath->query('descendant-or-self::*[@_disabled_delta or @_disabled_rename_from]', $oNode);
|
||||
foreach ($oModifiedNodeList as $oModifiedNode) {
|
||||
foreach (['_delta', '_rename_from'] as $sModificationFlag) {
|
||||
$sCurrentFlag = $oNode->getAttribute('_disabled'.$sModificationFlag);
|
||||
if (!empty($sCurrentFlag)) {
|
||||
$oModifiedNode->setAttribute($sModificationFlag, $sCurrentFlag);
|
||||
$oModifiedNode->removeAttribute('_disabled'.$sModificationFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move the node back in place
|
||||
$oParentNode->appendChild($oNode);
|
||||
}
|
||||
$oNode = $oNextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clean up the mess
|
||||
$this->RemoveNodeFromXPath("/itop_design/meta/previous_versions/previous_version_$sVersion", false);
|
||||
$this->RemoveEmptyNodeFromXPath("/itop_design/meta/previous_versions");
|
||||
$this->RemoveEmptyNodeFromXPath("/itop_design/meta");
|
||||
}
|
||||
|
||||
private function GetOrBuildParentNode($sXPath)
|
||||
{
|
||||
$oXPath = new DOMXPath($this->oDocument);
|
||||
$oParentNode = $oXPath->query($sXPath)->item(0);
|
||||
if ($oParentNode) {
|
||||
return $oParentNode;
|
||||
}
|
||||
|
||||
// Try to build the parent node
|
||||
$iLastSlashPos = strrpos($sXPath, '/');
|
||||
$sParentXPath = substr($sXPath, 0, $iLastSlashPos);
|
||||
$oParentNode = $this->GetOrBuildParentNode($sParentXPath);
|
||||
if (!$oParentNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sNodeXPath = substr($sXPath, $iLastSlashPos + 1);
|
||||
if (preg_match("/(?<tag>\w+)(\[@id=\"(?<id>\w+)\")?/", $sNodeXPath, $aMatches)) {
|
||||
$oNode = $this->oDocument->createElement($aMatches['tag']);
|
||||
if (isset($aMatches['id'])) {
|
||||
$oNode->setAttribute('id', $aMatches['id']);
|
||||
}
|
||||
$oParentNode->appendChild($oNode);
|
||||
return $oNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function RemoveEmptyNodeFromXPath($sXPath, $bStoreThisNodeInMetaVersion = false)
|
||||
{
|
||||
$oXPath = new DOMXPath($this->oDocument);
|
||||
$oNodeToRemove = $oXPath->query($sXPath)->item(0);
|
||||
if (is_null($oNodeToRemove)) {
|
||||
return;
|
||||
}
|
||||
$oNode = $oNodeToRemove->firstChild;
|
||||
while ($oNode) {
|
||||
if ($oNode->nodeType == XML_ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
$oNode = $oNode->nextSibling;
|
||||
}
|
||||
$this->RemoveNodeFromXPath($sXPath, $bStoreThisNodeInMetaVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPath
|
||||
* @param bool $bStoreThisNodeInMetaVersion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function RemoveNodeFromXPath($sPath, $bStoreThisNodeInMetaVersion = true)
|
||||
private function RemoveNodeFromXPath($sPath)
|
||||
{
|
||||
$oXPath = new DOMXPath($this->oDocument);
|
||||
|
||||
@@ -1106,56 +976,9 @@ class iTopDesignFormat
|
||||
foreach ($oNodeList as $oNode)
|
||||
{
|
||||
$this->LogWarning('Node '.self::GetItopNodePath($oNode).' is irrelevant in this version, it will be removed.');
|
||||
if ($bStoreThisNodeInMetaVersion && $this->bKeepObsoleteNodes && $this->sKeepVersion) {
|
||||
// Move the node to <Meta> to keep it safe for backward migration
|
||||
$oItopDesignNode = $this->GetOrCreateNode('/itop_design', 'itop_design', null);
|
||||
$oMetaNode = $this->GetOrCreateNode('meta', 'meta', $oItopDesignNode);
|
||||
$oPreviousVersionsNode = $this->GetOrCreateNode('previous_versions', 'previous_versions', $oMetaNode);
|
||||
$sVersion = str_replace('.', '_', $this->sKeepVersion);
|
||||
$oPreviousVersionNode = $this->GetOrCreateNode("previous_version_$sVersion", "previous_version_$sVersion", $oPreviousVersionsNode);
|
||||
$oTrashedNodeList = $this->GetOrCreateNode('trashed_nodes', 'trashed_nodes', $oPreviousVersionNode);
|
||||
|
||||
$iNextId = str_replace('.', '', uniqid('', true));
|
||||
$oTrashedNode = $this->GetOrCreateNode("trashed_node[@id='$iNextId']", 'trashed_node', $oTrashedNodeList);
|
||||
$oTrashedNode->setAttribute('id', $iNextId);
|
||||
$oTrashedNode->setAttribute('_delta', 'define');
|
||||
$oXPathNode = $this->GetOrCreateNode('parent_xpath', 'parent_xpath', $oTrashedNode);
|
||||
$oParentNode = $oNode->parentNode;
|
||||
if ($oParentNode instanceof DOMElement) {
|
||||
$sParentXPath = static::GetNodeXPath($oParentNode);
|
||||
$oXPathNode->appendChild(new DOMText($sParentXPath));
|
||||
}
|
||||
$oNodeTreeNode = $this->GetOrCreateNode('node_tree', 'node_tree', $oTrashedNode);
|
||||
|
||||
// Store the modification flags
|
||||
$oModifiedNodeList = $oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode);
|
||||
foreach ($oModifiedNodeList as $oModifiedNode) {
|
||||
foreach (['_delta', '_rename_from'] as $sModificationFlag) {
|
||||
$sCurrentFlag = $oNode->getAttribute($sModificationFlag);
|
||||
if (!empty($sCurrentFlag)) {
|
||||
$oModifiedNode->setAttribute('_disabled'.$sModificationFlag, $sCurrentFlag);
|
||||
$oModifiedNode->removeAttribute($sModificationFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oNodeTreeNode->appendChild($oNode);
|
||||
} else {
|
||||
$this->DeleteNode($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function GetOrCreateNode($sXPath, $sName, $oRootNode)
|
||||
{
|
||||
$oXPath = new DOMXPath($this->oDocument);
|
||||
$oNode = $oXPath->query($sXPath, $oRootNode)->item(0);
|
||||
if (is_null($oNode)) {
|
||||
$oNode = $oRootNode->ownerDocument->createElement($sName);
|
||||
$oRootNode->appendChild($oNode);
|
||||
}
|
||||
return $oNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a collection node by removing the _delta="define" on it and moving it to the item nodes.
|
||||
@@ -1343,31 +1166,4 @@ class iTopDesignFormat
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TEXT of the current node (possibly from several child nodes)
|
||||
* @param null $sDefault
|
||||
* @return null|string
|
||||
*/
|
||||
protected function GetText($oNode, $sDefault = null)
|
||||
{
|
||||
$sText = null;
|
||||
foreach($oNode->childNodes as $oChildNode)
|
||||
{
|
||||
if ($oChildNode instanceof \DOMText)
|
||||
{
|
||||
if (is_null($sText)) $sText = '';
|
||||
$sText .= $oChildNode->wholeText;
|
||||
}
|
||||
}
|
||||
if (is_null($sText))
|
||||
{
|
||||
return $sDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sText;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user