diff --git a/setup/itopdesignformat.class.inc.php b/setup/itopdesignformat.class.inc.php new file mode 100644 index 000000000..95bdc1ae3 --- /dev/null +++ b/setup/itopdesignformat.class.inc.php @@ -0,0 +1,171 @@ + + +/** + * Utility to upgrade the format of a given XML datamodel to the latest version + * The datamodel is supplied as a loaded DOMDocument and modified in-place. + * + * Usage: + * + * $oDocument = new DOMDocument(); + * $oDocument->load($sXMLFile); + * $aLog = array(); + * $oFormat = new iTopDesignFormat($oDocument); + * if ($oFormat->Upgrade($aLog)) + * { + * $oDocument->save($sXMLFile); + * } + * else + * { + * echo "Error, failed to upgrade the format, reason(s):\n".implode("\n", $aLog); + * } + */ + + define('ITOP_DESIGN_LATEST_VERSION', '1.1'); + +class iTopDesignFormat +{ + /** + * The Document to work on + * @var DOMDocument + */ + protected $oDocument; + + /** + * Creation from a loaded DOMDocument + * @param DOMDocument $oDocument The document to transform + */ + public function __construct(DOMDocument $oDocument) + { + $this->oDocument = $oDocument; + } + + /** + * 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 Array $aLog Array (as a reference) to gather the log results (errors, etc) + * @param string $sTargetVersion The desired version (or the latest possible version if not specified) + */ + public function Upgrade(&$aLog, $sTargetVersion = ITOP_DESIGN_LATEST_VERSION) + { + $oXPath = new DOMXPath($this->oDocument); + // Retrieve the version number + $oNodeList = $oXPath->query('/itop_design'); + if ($oNodeList->length == 0) + { + // Hmm, not an iTop Data Model file... + $aLog[] = "File format, no root tag found"; + return false; + } + else + { + $sVersion = $oNodeList->item(0)->getAttribute('version'); + switch($sVersion) + { + case '': // No version, assume 1.0 !! + case '1.0': + $bRet = $this->From10To11(); + if ($bRet) + { + // Update the version number + $oNodeList->item(0)->setAttribute('version', '1.1'); + } + return true; + break; + + case '1.1': + return true; // Nothing to do, the document is already at the most recent version + break; + + default: + $aLog[] = "Unknown format version: $sVersion"; + return false; // unknown versions are not supported + } + } + } + + /** + * Upgrade the format from version 1.0 to 1.1 + * @return boolean true on success, false otherwise + */ + protected function From10To11() + { + // Adjust the XML to transparently add an id (=stimulus) on all life-cycle transitions + // which don't already have one + $oXPath = new DOMXPath($this->oDocument); + $oNodeList = $oXPath->query('/itop_design/classes//class/lifecycle/states/state/transitions/transition/stimulus'); + foreach ($oNodeList as $oNode) + { + $oNode->parentNode->SetAttribute('id', $oNode->textContent); + $this->DeleteNode($oNode); + } + + // Adjust the XML to transparently add an id (=percent) on all thresholds of stopwatches + // which don't already have one + $oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeStopWatch']/thresholds/threshold/percent"); + foreach ($oNodeList as $oNode) + { + $oNode->parentNode->SetAttribute('id', $oNode->textContent); + $this->DeleteNode($oNode); + } + + // Adjust the XML to transparently add an id (=action:) on all allowed actions (profiles) + // which don't already have one + $oNodeList = $oXPath->query('/itop_design/user_rights/profiles/profile/groups/group/actions/action'); + foreach ($oNodeList as $oNode) + { + if ($oNode->getAttribute('id') == '') + { + $oNode->SetAttribute('id', 'action:' . $oNode->getAttribute('xsi:type')); + $oNode->removeAttribute('xsi:type'); + } + elseif ($oNode->getAttribute('xsi:type') == 'stimulus') + { + $oNode->SetAttribute('id', 'stimulus:' . $oNode->getAttribute('id')); + $oNode->removeAttribute('xsi:type'); + } + } + + // Adjust the XML to transparently add an id (=value) on all values of an enum which don't already have one. + // This enables altering an enum for just adding/removing one value, intead of redefining the whole list of values. + $oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeEnum']/values/value"); + foreach ($oNodeList as $oNode) + { + if ($oNode->getAttribute('id') == '') + { + $oNode->SetAttribute('id', $oNode->textContent); + } + } + + return true; + } + + /** + * Delete a node from the DOM and make sure to also remove the immediately following line break (DOMText), if any. + * This prevents generating empty lines in the middle of the XML + * @param DOMNode $oNode + */ + protected function DeleteNode($oNode) + { + if ( $oNode->nextSibling && ($oNode->nextSibling instanceof DOMText) && ($oNode->nextSibling->isWhitespaceInElementContent()) ) + { + $oNode->parentNode->removeChild($oNode->nextSibling); + } + $oNode->parentNode->removeChild($oNode); + } +} \ No newline at end of file diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php index fa08ebbad..6b4781ee8 100644 --- a/setup/modelfactory.class.inc.php +++ b/setup/modelfactory.class.inc.php @@ -25,6 +25,7 @@ require_once(APPROOT.'setup/moduleinstaller.class.inc.php'); +require_once(APPROOT.'setup/itopdesignformat.class.inc.php'); /** * ModelFactoryModule: the representation of a Module (i.e. element that can be selected during the setup) @@ -200,6 +201,7 @@ class ModelFactory $this->oDOMDocument = new MFDocument(); $this->oRoot = $this->oDOMDocument->CreateElement('itop_design'); $this->oRoot->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance"); + $this->oRoot->setAttribute('version', ITOP_DESIGN_LATEST_VERSION); $this->oDOMDocument->AppendChild($this->oRoot); $this->oModules = $this->oDOMDocument->CreateElement('loaded_modules'); $this->oRoot->AppendChild($this->oModules); @@ -432,7 +434,9 @@ class ModelFactory } } - self::UpgradeDocument($oDocument); + $oFormat = new iTopDesignFormat($oDocument); + $aErrorLog = array(); + if (!$oFormat->Upgrade($aErrorLog)) throw new Exception("Cannot load module $sModuleName, failed to upgrade to datamodel format of: $sXmlFile. Reason(s): ".implode("\n", $aErrorLog)); $oDeltaRoot = $oDocument->childNodes->item(0); $this->LoadDelta($oDeltaRoot, $this->oDOMDocument); @@ -513,63 +517,6 @@ class ModelFactory throw new Exception('Error loading module "'.$oModule->GetName().'": '.$e->getMessage().' - Loaded modules: '.implode(',', $aLoadedModuleNames)); } } - - /** - * Make adjustements to the DOM to migrate it to the latest version - * @param DOMDocument $oDocument - */ - public static function UpgradeDocument(DOMDocument $oDocument) - { - // Adjust the XML to transparently add an id (=stimulus) on all life-cycle transitions - // which don't already have one - $oXPath = new DOMXPath($oDocument); - $oNodeList = $oXPath->query('/itop_design/classes//class/lifecycle/states/state/transitions/transition/stimulus'); - foreach ($oNodeList as $oNode) - { - if ($oNode->parentNode->getAttribute('id') == '') - { - $oNode->parentNode->SetAttribute('id', $oNode->textContent); - $oNode->parentNode->removeChild($oNode); - } - } - - // Adjust the XML to transparently add an id (=percent) on all thresholds of stopwatches - // which don't already have one - $oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeStopWatch']/thresholds/threshold/percent"); - foreach ($oNodeList as $oNode) - { - $oNode->parentNode->SetAttribute('id', $oNode->textContent); - $oNode->parentNode->removeChild($oNode); - } - - // Adjust the XML to transparently add an id (=action:) on all allowed actions (profiles) - // which don't already have one - $oNodeList = $oXPath->query('/itop_design/user_rights/profiles/profile/groups/group/actions/action'); - foreach ($oNodeList as $oNode) - { - if ($oNode->getAttribute('id') == '') - { - $oNode->SetAttribute('id', 'action:' . $oNode->getAttribute('xsi:type')); - $oNode->removeAttribute('xsi:type'); - } - elseif ($oNode->getAttribute('xsi:type') == 'stimulus') - { - $oNode->SetAttribute('id', 'stimulus:' . $oNode->getAttribute('id')); - $oNode->removeAttribute('xsi:type'); - } - } - - // Adjust the XML to transparently add an id (=value) on all values of an enum which don't already have one. - // This enables altering an enum for just adding/removing one value, intead of redefining the whole list of values. - $oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeEnum']/values/value"); - foreach ($oNodeList as $oNode) - { - if ($oNode->getAttribute('id') == '') - { - $oNode->SetAttribute('id', $oNode->textContent); - } - } - } /** * Collects the PHP Dict entries into the ModelFactory for transforming the dictionary into an XML structure @@ -1866,6 +1813,7 @@ class MFDocument extends DOMDocument { $oRootNode = $this->createElement('itop_design'); // make sure that the document is not empty $oRootNode->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance"); + $oRootNode->setAttribute('version', ITOP_DESIGN_LATEST_VERSION); $this->appendChild($oRootNode); } return parent::saveXML();