modelfactory.class.inc.php 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048
  1. <?php
  2. // Copyright (C) 2010-2013 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * ModelFactory: in-memory manipulation of the XML MetaModel
  20. *
  21. * @copyright Copyright (C) 2010-2012 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. require_once(APPROOT.'setup/moduleinstaller.class.inc.php');
  25. require_once(APPROOT.'setup/itopdesignformat.class.inc.php');
  26. /**
  27. * ModelFactoryModule: the representation of a Module (i.e. element that can be selected during the setup)
  28. * @package ModelFactory
  29. */
  30. class MFModule
  31. {
  32. protected $sId;
  33. protected $sName;
  34. protected $sVersion;
  35. protected $sRootDir;
  36. protected $sLabel;
  37. protected $aDataModels;
  38. public function __construct($sId, $sRootDir, $sLabel)
  39. {
  40. $this->sId = $sId;
  41. list($this->sName, $this->sVersion) = ModuleDiscovery::GetModuleName($sId);
  42. if (strlen($this->sVersion) == 0)
  43. {
  44. $this->sVersion = '1.0.0';
  45. }
  46. $this->sRootDir = $sRootDir;
  47. $this->sLabel = $sLabel;
  48. $this->aDataModels = array();
  49. // Scan the module's root directory to find the datamodel(*).xml files
  50. if ($hDir = opendir($sRootDir))
  51. {
  52. // This is the correct way to loop over the directory. (according to the documentation)
  53. while (($sFile = readdir($hDir)) !== false)
  54. {
  55. if (preg_match('/^datamodel(.*)\.xml$/i', $sFile, $aMatches))
  56. {
  57. $this->aDataModels[] = $this->sRootDir.'/'.$aMatches[0];
  58. }
  59. }
  60. closedir($hDir);
  61. }
  62. }
  63. public function GetId()
  64. {
  65. return $this->sId;
  66. }
  67. public function GetName()
  68. {
  69. return $this->sName;
  70. }
  71. public function GetVersion()
  72. {
  73. return $this->sVersion;
  74. }
  75. public function GetLabel()
  76. {
  77. return $this->sLabel;
  78. }
  79. public function GetRootDir()
  80. {
  81. return $this->sRootDir;
  82. }
  83. public function GetModuleDir()
  84. {
  85. return basename($this->sRootDir);
  86. }
  87. public function GetDataModelFiles()
  88. {
  89. return $this->aDataModels;
  90. }
  91. /**
  92. * List all classes in this module
  93. */
  94. public function ListClasses()
  95. {
  96. return array();
  97. }
  98. public function GetDictionaryFiles()
  99. {
  100. $aDictionaries = array();
  101. if ($hDir = opendir($this->sRootDir))
  102. {
  103. while (($sFile = readdir($hDir)) !== false)
  104. {
  105. $aMatches = array();
  106. if (preg_match("/^[^\\.]+.dict.".$this->sName.".php$/i", $sFile, $aMatches)) // Dictionary files are named like <Lang>.dict.<ModuleName>.php
  107. {
  108. $aDictionaries[] = $this->sRootDir.'/'.$sFile;
  109. }
  110. }
  111. closedir($hDir);
  112. }
  113. return $aDictionaries;
  114. }
  115. }
  116. /**
  117. * MFDeltaModule: an optional module, made of a single file
  118. * @package ModelFactory
  119. */
  120. class MFDeltaModule extends MFModule
  121. {
  122. public function __construct($sDeltaFile)
  123. {
  124. $this->sId = 'datamodel-delta';
  125. $this->sName = 'delta';
  126. $this->sVersion = '1.0';
  127. $this->sRootDir = '';
  128. $this->sLabel = 'Additional Delta';
  129. $this->aDataModels = array($sDeltaFile);
  130. }
  131. public function GetName()
  132. {
  133. return ''; // Objects created inside this pseudo module retain their original module's name
  134. }
  135. public function GetRootDir()
  136. {
  137. return '';
  138. }
  139. public function GetModuleDir()
  140. {
  141. return '';
  142. }
  143. public function GetDictionaryFiles()
  144. {
  145. return array();
  146. }
  147. }
  148. /**
  149. * ModelFactory: the class that manages the in-memory representation of the XML MetaModel
  150. * @package ModelFactory
  151. */
  152. class ModelFactory
  153. {
  154. protected $aRootDirs;
  155. protected $oDOMDocument;
  156. protected $oRoot;
  157. protected $oModules;
  158. protected $oClasses;
  159. protected $oMenus;
  160. protected $oDictionaries;
  161. static protected $aLoadedClasses;
  162. static protected $aWellKnownParents = array('DBObject', 'CMDBObject','cmdbAbstractObject');
  163. // static protected $aWellKnownMenus = array('DataAdministration', 'Catalogs', 'ConfigManagement', 'Contact', 'ConfigManagementCI', 'ConfigManagement:Shortcuts', 'ServiceManagement');
  164. static protected $aLoadedModules;
  165. static protected $aLoadErrors;
  166. protected $aDict;
  167. protected $aDictKeys;
  168. public function __construct($aRootDirs, $aRootNodeExtensions = array())
  169. {
  170. $this->aDict = array();
  171. $this->aDictKeys = array();
  172. $this->aRootDirs = $aRootDirs;
  173. $this->oDOMDocument = new MFDocument();
  174. $this->oRoot = $this->oDOMDocument->CreateElement('itop_design');
  175. $this->oRoot->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
  176. $this->oRoot->setAttribute('version', ITOP_DESIGN_LATEST_VERSION);
  177. $this->oDOMDocument->AppendChild($this->oRoot);
  178. $this->oModules = $this->oDOMDocument->CreateElement('loaded_modules');
  179. $this->oRoot->AppendChild($this->oModules);
  180. $this->oClasses = $this->oDOMDocument->CreateElement('classes');
  181. $this->oRoot->AppendChild($this->oClasses);
  182. $this->oDictionaries = $this->oDOMDocument->CreateElement('dictionaries');
  183. $this->oRoot->AppendChild($this->oDictionaries);
  184. foreach (self::$aWellKnownParents as $sWellKnownParent)
  185. {
  186. $this->AddWellKnownParent($sWellKnownParent);
  187. }
  188. $this->oMenus = $this->oDOMDocument->CreateElement('menus');
  189. $this->oRoot->AppendChild($this->oMenus);
  190. $this->oMeta = $this->oDOMDocument->CreateElement('meta');
  191. $this->oRoot->AppendChild($this->oMeta);
  192. foreach($aRootNodeExtensions as $sElementName)
  193. {
  194. $oElement = $this->oDOMDocument->CreateElement($sElementName);
  195. $this->oRoot->AppendChild($oElement);
  196. }
  197. self::$aLoadedModules = array();
  198. self::$aLoadErrors = array();
  199. libxml_use_internal_errors(true);
  200. }
  201. public function Dump($oNode = null, $bReturnRes = false)
  202. {
  203. if (is_null($oNode))
  204. {
  205. $oNode = $this->oRoot;
  206. }
  207. return $oNode->Dump($bReturnRes);
  208. }
  209. public function LoadFromFile($sCacheFile)
  210. {
  211. $this->oDOMDocument->load($sCacheFile);
  212. $this->oRoot = $this->oDOMDocument->firstChild;
  213. $this->oModules = $this->oRoot->getElementsByTagName('loaded_modules')->item(0);
  214. self::$aLoadedModules = array();
  215. foreach($this->oModules->getElementsByTagName('module') as $oModuleNode)
  216. {
  217. $sId = $oModuleNode->getAttribute('id');
  218. $sRootDir = $oModuleNode->GetChildText('root_dir');
  219. $sLabel = $oModuleNode->GetChildText('label');
  220. self::$aLoadedModules[] = new MFModule($sId, $sRootDir, $sLabel);
  221. }
  222. }
  223. public function SaveToFile($sCacheFile)
  224. {
  225. $this->oDOMDocument->save($sCacheFile);
  226. }
  227. /**
  228. * To progressively replace LoadModule
  229. * @param xxx xxx
  230. */
  231. public function LoadDelta($oSourceNode, $oTargetParentNode)
  232. {
  233. if (!$oSourceNode instanceof DOMElement) return;
  234. //echo "Load $oSourceNode->tagName::".$oSourceNode->getAttribute('id')." (".$oSourceNode->getAttribute('_delta').")<br/>\n";
  235. $oTarget = $this->oDOMDocument;
  236. if (($oSourceNode->tagName == 'class') && ($oSourceNode->parentNode->tagName == 'classes') && ($oSourceNode->parentNode->parentNode->tagName == 'itop_design'))
  237. {
  238. $sParentId = $oSourceNode->GetChildText('parent');
  239. if ($oSourceNode->getAttribute('_delta') == 'define')
  240. {
  241. // This tag is organized in hierarchy: determine the real parent node (as a subnode of the current node)
  242. $oTargetParentNode = $oTarget->GetNodeById('/itop_design/classes//class', $sParentId)->item(0);
  243. if (!$oTargetParentNode)
  244. {
  245. echo "Dumping target doc - looking for '$sParentId'<br/>\n";
  246. $this->oDOMDocument->firstChild->Dump();
  247. throw new Exception("could not find parent node for $oSourceNode->tagName(id:".$oSourceNode->getAttribute('id').") with parent id $sParentId");
  248. }
  249. }
  250. else
  251. {
  252. $oTargetNode = $oTarget->GetNodeById('/itop_design/classes//class', $oSourceNode->getAttribute('id'))->item(0);
  253. if (!$oTargetNode)
  254. {
  255. echo "Dumping target doc - looking for '".$oSourceNode->getAttribute('id')."'<br/>\n";
  256. $this->oDOMDocument->firstChild->Dump();
  257. throw new Exception("could not find node for $oSourceNode->tagName(id:".$oSourceNode->getAttribute('id').")");
  258. }
  259. else
  260. {
  261. $oTargetParentNode = $oTargetNode->parentNode;
  262. if (($oSourceNode->getAttribute('_delta') == 'redefine') && ($oTargetParentNode->getAttribute('id') != $sParentId))
  263. {
  264. // A class that has moved <=> deletion and creation elsewhere
  265. $oTargetParentNode = $oTarget->GetNodeById('/itop_design/classes//class', $sParentId)->item(0);
  266. $oTargetNode->Delete();
  267. $oSourceNode->setAttribute('_delta', 'define');
  268. }
  269. }
  270. }
  271. }
  272. switch ($oSourceNode->getAttribute('_delta'))
  273. {
  274. case 'must_exist':
  275. case 'merge':
  276. case '':
  277. $bMustExist = ($oSourceNode->getAttribute('_delta') == 'must_exist');
  278. $sSearchId = $oSourceNode->hasAttribute('_rename_from') ? $oSourceNode->getAttribute('_rename_from') : $oSourceNode->getAttribute('id');
  279. $oTargetNode = $oSourceNode->MergeInto($oTargetParentNode, $sSearchId, $bMustExist);
  280. foreach($oSourceNode->childNodes as $oSourceChild)
  281. {
  282. // Continue deeper
  283. $this->LoadDelta($oSourceChild, $oTargetNode);
  284. }
  285. break;
  286. case 'define_if_not_exists':
  287. $oExistingNode = $oTargetParentNode->_FindChildNode($oSourceNode);
  288. if ( ($oExistingNode == null) || ($oExistingNode->getAttribute('_alteration') == 'removed') )
  289. {
  290. // Same as 'define' below
  291. $oTargetNode = $oTarget->ImportNode($oSourceNode, true);
  292. $oTargetParentNode->AddChildNode($oTargetNode);
  293. }
  294. else
  295. {
  296. $oTargetNode = $oExistingNode;
  297. }
  298. $oTargetNode->setAttribute('_alteration', 'needed');
  299. break;
  300. case 'define':
  301. // New node - copy child nodes as well
  302. $oTargetNode = $oTarget->ImportNode($oSourceNode, true);
  303. $oTargetParentNode->AddChildNode($oTargetNode);
  304. break;
  305. case 'redefine':
  306. // Replace the existing node by the given node - copy child nodes as well
  307. $oTargetNode = $oTarget->ImportNode($oSourceNode, true);
  308. $sSearchId = $oSourceNode->hasAttribute('_rename_from') ? $oSourceNode->getAttribute('_rename_from') : $oSourceNode->getAttribute('id');
  309. $oTargetParentNode->RedefineChildNode($oTargetNode, $sSearchId);
  310. break;
  311. case 'delete':
  312. $oTargetNode = $oTargetParentNode->_FindChildNode($oSourceNode);
  313. if ( ($oTargetNode == null) || ($oTargetNode->getAttribute('_alteration') == 'removed') )
  314. {
  315. throw new Exception("Trying to delete node for {$oSourceNode->tagName} (id:".$oSourceNode->getAttribute('id').") under {$oTargetParentNode->tagName} (id:".$oTargetParentNode->getAttribute('id').'). but nothing found.');
  316. }
  317. $oTargetNode->Delete();
  318. break;
  319. }
  320. if ($oSourceNode->hasAttribute('_rename_from'))
  321. {
  322. $oTargetNode->Rename($oSourceNode->getAttribute('id'));
  323. }
  324. if ($oTargetNode->hasAttribute('_delta'))
  325. {
  326. $oTargetNode->removeAttribute('_delta');
  327. }
  328. }
  329. /**
  330. * Loads the definitions corresponding to the given Module
  331. * @param MFModule $oModule
  332. * @param Array $aLanguages The list of languages to process (for the dictionaries). If empty all languages are kept
  333. */
  334. public function LoadModule(MFModule $oModule, $aLanguages = array())
  335. {
  336. try
  337. {
  338. $aDataModels = $oModule->GetDataModelFiles();
  339. $sModuleName = $oModule->GetName();
  340. $aClasses = array();
  341. self::$aLoadedModules[] = $oModule;
  342. // For persistence in the cache
  343. $oModuleNode = $this->oDOMDocument->CreateElement('module');
  344. $oModuleNode->setAttribute('id', $oModule->GetId());
  345. $oModuleNode->AppendChild($this->oDOMDocument->CreateElement('root_dir', $oModule->GetRootDir()));
  346. $oModuleNode->AppendChild($this->oDOMDocument->CreateElement('label', $oModule->GetLabel()));
  347. $this->oModules->AppendChild($oModuleNode);
  348. foreach($aDataModels as $sXmlFile)
  349. {
  350. $oDocument = new MFDocument();
  351. libxml_clear_errors();
  352. $oDocument->load($sXmlFile);
  353. //$bValidated = $oDocument->schemaValidate(APPROOT.'setup/itop_design.xsd');
  354. $aErrors = libxml_get_errors();
  355. if (count($aErrors) > 0)
  356. {
  357. self::$aLoadErrors[$sModuleName] = $aErrors;
  358. return;
  359. }
  360. $oXPath = new DOMXPath($oDocument);
  361. $oNodeList = $oXPath->query('/itop_design/classes//class');
  362. foreach($oNodeList as $oNode)
  363. {
  364. if ($oNode->getAttribute('_created_in') == '')
  365. {
  366. $oNode->SetAttribute('_created_in', $sModuleName);
  367. }
  368. }
  369. $oNodeList = $oXPath->query('/itop_design/constants/constant');
  370. foreach($oNodeList as $oNode)
  371. {
  372. if ($oNode->getAttribute('_created_in') == '')
  373. {
  374. $oNode->SetAttribute('_created_in', $sModuleName);
  375. }
  376. }
  377. $oNodeList = $oXPath->query('/itop_design/menus/menu');
  378. foreach($oNodeList as $oNode)
  379. {
  380. if ($oNode->getAttribute('_created_in') == '')
  381. {
  382. $oNode->SetAttribute('_created_in', $sModuleName);
  383. }
  384. }
  385. $oUserRightsNode = $oXPath->query('/itop_design/user_rights')->item(0);
  386. if ($oUserRightsNode)
  387. {
  388. if ($oUserRightsNode->getAttribute('_created_in') == '')
  389. {
  390. $oUserRightsNode->SetAttribute('_created_in', $sModuleName);
  391. }
  392. }
  393. $oFormat = new iTopDesignFormat($oDocument);
  394. if (!$oFormat->Convert())
  395. {
  396. $sError = implode(', ', $oFormat->GetErrors());
  397. throw new Exception("Cannot load module $sModuleName, failed to upgrade to datamodel format of: $sXmlFile. Reason(s): $sError");
  398. }
  399. $oDeltaRoot = $oDocument->childNodes->item(0);
  400. $this->LoadDelta($oDeltaRoot, $this->oDOMDocument);
  401. }
  402. $aDictionaries = $oModule->GetDictionaryFiles();
  403. try
  404. {
  405. $this->ResetTempDictionary();
  406. foreach($aDictionaries as $sPHPFile)
  407. {
  408. $sDictFileContents = file_get_contents($sPHPFile);
  409. $sDictFileContents = str_replace(array('<'.'?'.'php', '?'.'>'), '', $sDictFileContents);
  410. $sDictFileContents = str_replace('Dict::Add', '$this->AddToTempDictionary', $sDictFileContents);
  411. eval($sDictFileContents);
  412. }
  413. foreach ($this->aDict as $sLanguageCode => $aDictDefinition)
  414. {
  415. if ((count($aLanguages) > 0 ) && !in_array($sLanguageCode, $aLanguages))
  416. {
  417. // skip some languages if the parameter says so
  418. continue;
  419. }
  420. $oNodes = $this->GetNodeById('dictionary', $sLanguageCode, $this->oDictionaries);
  421. if ($oNodes->length == 0)
  422. {
  423. $oXmlDict = $this->oDOMDocument->CreateElement('dictionary');
  424. $oXmlDict->setAttribute('id', $sLanguageCode);
  425. $this->oDictionaries->AddChildNode($oXmlDict);
  426. $oXmlEntries = $this->oDOMDocument->CreateElement('english_description', $aDictDefinition['english_description']);
  427. $oXmlDict->AppendChild($oXmlEntries);
  428. $oXmlEntries = $this->oDOMDocument->CreateElement('localized_description', $aDictDefinition['localized_description']);
  429. $oXmlDict->AppendChild($oXmlEntries);
  430. $oXmlEntries = $this->oDOMDocument->CreateElement('entries');
  431. $oXmlDict->AppendChild($oXmlEntries);
  432. }
  433. else
  434. {
  435. $oXmlDict = $oNodes->item(0);
  436. $oXmlEntries = $oXmlDict->GetUniqueElement('entries');
  437. }
  438. foreach ($aDictDefinition['entries'] as $sCode => $sLabel)
  439. {
  440. $oXmlEntry = $this->oDOMDocument->CreateElement('entry');
  441. $oXmlEntry->setAttribute('id', $sCode);
  442. $oXmlValue = $this->oDOMDocument->CreateCDATASection($sLabel);
  443. $oXmlEntry->appendChild($oXmlValue);
  444. if (array_key_exists($sLanguageCode, $this->aDictKeys) && array_key_exists($sCode, $this->aDictKeys[$sLanguageCode]))
  445. {
  446. $oXmlEntries->RedefineChildNode($oXmlEntry);
  447. }
  448. else
  449. {
  450. $oXmlEntries->appendChild($oXmlEntry);
  451. }
  452. $this->aDictKeys[$sLanguageCode][$sCode] = true;
  453. }
  454. }
  455. }
  456. catch(Exception $e)
  457. {
  458. throw new Exception('Failed to load dictionary file "'.$sPHPFile.'", reason: '.$e->getMessage());
  459. }
  460. }
  461. catch(Exception $e)
  462. {
  463. $aLoadedModuleNames = array();
  464. foreach (self::$aLoadedModules as $oModule)
  465. {
  466. $aLoadedModuleNames[] = $oModule->GetName();
  467. }
  468. throw new Exception('Error loading module "'.$oModule->GetName().'": '.$e->getMessage().' - Loaded modules: '.implode(',', $aLoadedModuleNames));
  469. }
  470. }
  471. /**
  472. * Collects the PHP Dict entries into the ModelFactory for transforming the dictionary into an XML structure
  473. * @param string $sLanguageCode The language code
  474. * @param string $sEnglishLanguageDesc English description of the language (unused but kept for API compatibility)
  475. * @param string $sLocalizedLanguageDesc Localized description of the language (unused but kept for API compatibility)
  476. * @param hash $aEntries The entries to load: string_code => translation
  477. */
  478. protected function AddToTempDictionary($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
  479. {
  480. $this->aDict[$sLanguageCode]['english_description'] = $sEnglishLanguageDesc;
  481. $this->aDict[$sLanguageCode]['localized_description'] = $sLocalizedLanguageDesc;
  482. if (!array_key_exists('entries', $this->aDict[$sLanguageCode]))
  483. {
  484. $this->aDict[$sLanguageCode]['entries'] = array();
  485. }
  486. foreach($aEntries as $sKey => $sValue)
  487. {
  488. $this->aDict[$sLanguageCode]['entries'][$sKey] = $sValue;
  489. }
  490. }
  491. protected function ResetTempDictionary()
  492. {
  493. $this->aDict = array();
  494. }
  495. /**
  496. * XML load errors (XML format and validation)
  497. */
  498. function HasLoadErrors()
  499. {
  500. return (count(self::$aLoadErrors) > 0);
  501. }
  502. function GetLoadErrors()
  503. {
  504. return self::$aLoadErrors;
  505. }
  506. function GetLoadedModules($bExcludeWorkspace = true)
  507. {
  508. if ($bExcludeWorkspace)
  509. {
  510. $aModules = array();
  511. foreach(self::$aLoadedModules as $oModule)
  512. {
  513. if (!$oModule instanceof MFWorkspace)
  514. {
  515. $aModules[] = $oModule;
  516. }
  517. }
  518. }
  519. else
  520. {
  521. $aModules = self::$aLoadedModules;
  522. }
  523. return $aModules;
  524. }
  525. function GetModule($sModuleName)
  526. {
  527. foreach(self::$aLoadedModules as $oModule)
  528. {
  529. if ($oModule->GetName() == $sModuleName) return $oModule;
  530. }
  531. return null;
  532. }
  533. public function CreateElement($sTagName, $sValue = '')
  534. {
  535. return $this->oDOMDocument->createElement($sTagName, $sValue);
  536. }
  537. public function GetNodeById($sXPath, $sId, $oContextNode = null)
  538. {
  539. return $this->oDOMDocument->GetNodeById($sXPath, $sId, $oContextNode);
  540. }
  541. /**
  542. * Apply extensibility rules into the DOM
  543. * @param array aRestrictionRules Array of array ('selectors' => array of XPaths, 'rules' => array of rules)
  544. * @return void
  545. */
  546. public function RestrictExtensibility($aRestrictionRules)
  547. {
  548. foreach ($aRestrictionRules as $aRestriction)
  549. {
  550. foreach ($aRestriction['selectors'] as $sSelector)
  551. {
  552. foreach($this->GetNodes($sSelector) as $oNode)
  553. {
  554. $oNode->RestrictExtensibility($aRestriction['rules']);
  555. }
  556. }
  557. }
  558. }
  559. /**
  560. * Check if the class specified by the given node already exists in the loaded DOM
  561. * @param DOMNode $oClassNode The node corresponding to the class to load
  562. * @throws Exception
  563. * @return bool True if the class exists, false otherwise
  564. */
  565. protected function ClassExists(DOMNode $oClassNode)
  566. {
  567. assert(false);
  568. if ($oClassNode->hasAttribute('id'))
  569. {
  570. $sClassName = $oClassNode->GetAttribute('id');
  571. }
  572. else
  573. {
  574. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  575. }
  576. return (array_key_exists($sClassName, self::$aLoadedClasses));
  577. }
  578. /**
  579. * Check if the class specified by the given name already exists in the loaded DOM
  580. * @param string $sClassName The node corresponding to the class to load
  581. * @throws Exception
  582. * @return bool True if the class exists, false otherwise
  583. */
  584. protected function ClassNameExists($sClassName)
  585. {
  586. return !is_null($this->GetClass($sClassName));
  587. }
  588. /**
  589. * Add the given class to the DOM
  590. * @param DOMNode $oClassNode
  591. * @param string $sModuleName The name of the module in which this class is declared
  592. * @throws Exception
  593. */
  594. public function AddClass(DOMNode $oClassNode, $sModuleName)
  595. {
  596. if ($oClassNode->hasAttribute('id'))
  597. {
  598. $sClassName = $oClassNode->GetAttribute('id');
  599. }
  600. else
  601. {
  602. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  603. }
  604. if ($this->ClassNameExists($oClassNode->getAttribute('id')))
  605. {
  606. throw new Exception("ModelFactory::AddClass: Cannot add the already existing class $sClassName");
  607. }
  608. $sParentClass = $oClassNode->GetChildText('parent', '');
  609. $oParentNode = $this->GetClass($sParentClass);
  610. if ($oParentNode == null)
  611. {
  612. throw new Exception("ModelFactory::AddClass: Cannot find the parent class of '$sClassName': '$sParentClass'");
  613. }
  614. else
  615. {
  616. if ($sModuleName != '')
  617. {
  618. $oClassNode->SetAttribute('_created_in', $sModuleName);
  619. }
  620. $oParentNode->AddChildNode($this->oDOMDocument->importNode($oClassNode, true));
  621. if (substr($sParentClass, 0, 1) == '/') // Convention for well known parent classes
  622. {
  623. // Remove the leading slash character
  624. $oParentNameNode = $oClassNode->GetOptionalElement('parent')->firstChild; // Get the DOMCharacterData node
  625. $oParentNameNode->data = substr($sParentClass, 1);
  626. }
  627. }
  628. }
  629. public function GetClassXMLTemplate($sName, $sIcon)
  630. {
  631. $sHeader = '<?'.'xml version="1.0" encoding="utf-8"?'.'>';
  632. return
  633. <<<EOF
  634. $sHeader
  635. <class id="$sName">
  636. <comment/>
  637. <properties>
  638. </properties>
  639. <naming format=""><attributes/></naming>
  640. <reconciliation><attributes/></reconciliation>
  641. <display_template/>
  642. <icon>$sIcon</icon>
  643. </properties>
  644. <fields/>
  645. <lifecycle/>
  646. <methods/>
  647. <presentation>
  648. <details><items/></details>
  649. <search><items/></search>
  650. <list><items/></list>
  651. </presentation>
  652. </class>
  653. EOF
  654. ;
  655. }
  656. /**
  657. * List all constants from the DOM, for a given module
  658. * @param string $sModuleName
  659. * @throws Exception
  660. */
  661. public function ListConstants($sModuleName)
  662. {
  663. return $this->GetNodes("/itop_design/constants/constant[@_created_in='$sModuleName']");
  664. }
  665. /**
  666. * List all classes from the DOM, for a given module
  667. * @param string $sModuleName
  668. * @throws Exception
  669. */
  670. public function ListClasses($sModuleName)
  671. {
  672. return $this->GetNodes("/itop_design/classes//class[@_created_in='$sModuleName']");
  673. }
  674. /**
  675. * List all classes from the DOM
  676. * @throws Exception
  677. */
  678. public function ListAllClasses()
  679. {
  680. return $this->GetNodes("/itop_design/classes//class");
  681. }
  682. /**
  683. * List top level (non abstract) classes having child classes
  684. * @throws Exception
  685. */
  686. public function ListRootClasses()
  687. {
  688. return $this->GetNodes("/itop_design/classes/class/class[class]");
  689. }
  690. public function GetClass($sClassName)
  691. {
  692. $oClassNode = $this->GetNodes("/itop_design/classes//class[@id='$sClassName']")->item(0);
  693. return $oClassNode;
  694. }
  695. public function AddWellKnownParent($sWellKnownParent)
  696. {
  697. $oWKClass = $this->oDOMDocument->CreateElement('class');
  698. $oWKClass->setAttribute('id', $sWellKnownParent);
  699. $this->oClasses->AppendChild($oWKClass);
  700. return $oWKClass;
  701. }
  702. public function GetChildClasses($oClassNode)
  703. {
  704. return $this->GetNodes("class", $oClassNode);
  705. }
  706. public function GetField($sClassName, $sAttCode)
  707. {
  708. if (!$this->ClassNameExists($sClassName))
  709. {
  710. return null;
  711. }
  712. $oClassNode = self::$aLoadedClasses[$sClassName];
  713. $oFieldNode = $this->GetNodes("fields/field[@id='$sAttCode']", $oClassNode)->item(0);
  714. if (($oFieldNode == null) && ($sParentClass = $oClassNode->GetChildText('parent')))
  715. {
  716. return $this->GetField($sParentClass, $sAttCode);
  717. }
  718. return $oFieldNode;
  719. }
  720. /**
  721. * List all classes from the DOM
  722. * @throws Exception
  723. */
  724. public function ListFields(DOMNode $oClassNode)
  725. {
  726. return $this->GetNodes("fields/field", $oClassNode);
  727. }
  728. /**
  729. * List all transitions from a given state
  730. * @param DOMNode $oStateNode The state
  731. * @throws Exception
  732. */
  733. public function ListTransitions(DOMNode $oStateNode)
  734. {
  735. return $this->GetNodes("transitions/transition", $oStateNode);
  736. }
  737. /**
  738. * List all states of a given class
  739. * @param DOMNode $oClassNode The class
  740. * @throws Exception
  741. */
  742. public function ListStates(DOMNode $oClassNode)
  743. {
  744. return $this->GetNodes("lifecycle/states/state", $oClassNode);
  745. }
  746. public function ApplyChanges()
  747. {
  748. $oNodes = $this->ListChanges();
  749. foreach($oNodes as $oNode)
  750. {
  751. $sOperation = $oNode->GetAttribute('_alteration');
  752. switch($sOperation)
  753. {
  754. case 'added':
  755. case 'replaced':
  756. case 'needed':
  757. // marked as added or modified, just reset the flag
  758. $oNode->removeAttribute('_alteration');
  759. break;
  760. case 'removed':
  761. // marked as deleted, let's remove the node from the tree
  762. $oNode->parentNode->removeChild($oNode);
  763. // TODO!!!!!!!
  764. //unset(self::$aLoadedClasses[$sClass]);
  765. break;
  766. }
  767. if ($oNode->hasAttribute('_old_id'))
  768. {
  769. $oNode->removeAttribute('_old_id');
  770. }
  771. }
  772. }
  773. public function ListChanges()
  774. {
  775. return $this->oDOMDocument->GetNodes('//*[@_alteration or @_old_id]', null, false /* not safe */);
  776. }
  777. /**
  778. * Import the node into the delta
  779. */
  780. protected function SetDeltaFlags($oNodeClone)
  781. {
  782. $sAlteration = $oNodeClone->getAttribute('_alteration');
  783. $oNodeClone->removeAttribute('_alteration');
  784. if ($oNodeClone->hasAttribute('_old_id'))
  785. {
  786. $oNodeClone->setAttribute('_rename_from', $oNodeClone->getAttribute('_old_id'));
  787. $oNodeClone->removeAttribute('_old_id');
  788. }
  789. switch ($sAlteration)
  790. {
  791. case '':
  792. if ($oNodeClone->hasAttribute('id'))
  793. {
  794. $oNodeClone->setAttribute('_delta', 'must_exist');
  795. }
  796. break;
  797. case 'added':
  798. $oNodeClone->setAttribute('_delta', 'define');
  799. break;
  800. case 'replaced':
  801. $oNodeClone->setAttribute('_delta', 'redefine');
  802. break;
  803. case 'removed':
  804. $oNodeClone->setAttribute('_delta', 'delete');
  805. break;
  806. case 'needed':
  807. $oNodeClone->setAttribute('_delta', 'define_if_not_exists');
  808. break;
  809. }
  810. return $oNodeClone;
  811. }
  812. /**
  813. * Create path for the delta
  814. * @param Array aMovedClasses The classes that have been moved in the hierarchy (deleted + created elsewhere)
  815. * @param DOMDocument oTargetDoc Where to attach the top of the hierarchy
  816. * @param MFElement oNode The node to import with its path
  817. */
  818. protected function ImportNodeAndPathDelta($aMovedClasses, $oTargetDoc, $oNode)
  819. {
  820. // Preliminary: skip the parent if this node is organized hierarchically into the DOM
  821. // Only class nodes are organized this way
  822. $oParent = $oNode->parentNode;
  823. if ($oNode->IsClassNode())
  824. {
  825. while (($oParent instanceof DOMElement) && ($oParent->IsClassNode()))
  826. {
  827. $oParent = $oParent->parentNode;
  828. }
  829. }
  830. // Recursively create the path for the parent
  831. if ($oParent instanceof DOMElement)
  832. {
  833. $oParentClone = $this->ImportNodeAndPathDelta($aMovedClasses, $oTargetDoc, $oParent);
  834. }
  835. else
  836. {
  837. // We've reached the top let's add the node into the root recipient
  838. $oParentClone = $oTargetDoc;
  839. }
  840. $sAlteration = $oNode->getAttribute('_alteration');
  841. if ($oNode->IsClassNode() && ($sAlteration != ''))
  842. {
  843. // Handle the moved classes
  844. //
  845. // Import the whole root node
  846. $oNodeClone = $oTargetDoc->importNode($oNode->cloneNode(true), true);
  847. $oParentClone->appendChild($oNodeClone);
  848. $this->SetDeltaFlags($oNodeClone);
  849. // Handle the moved classes found under the root node (or the root node itself)
  850. foreach($oNodeClone->GetNodes("descendant-or-self::class[@id]", false) as $oClassNode)
  851. {
  852. if (array_key_exists($oClassNode->getAttribute('id'), $aMovedClasses))
  853. {
  854. if ($sAlteration == 'removed')
  855. {
  856. // Remove that node: this specification will be overriden by the 'replaced' spec (see below)
  857. $oClassNode->parentNode->removeChild($oClassNode);
  858. }
  859. else
  860. {
  861. // Move the class at the root, with the flag 'modified'
  862. $oParentClone->appendChild($oClassNode);
  863. $oClassNode->setAttribute('_alteration', 'replaced');
  864. $this->SetDeltaFlags($oClassNode);
  865. }
  866. }
  867. }
  868. }
  869. else
  870. {
  871. // Look for the node into the parent node
  872. // Note: this is an identified weakness of the algorithm,
  873. // because for each node modified, and each node of its path
  874. // we will have to lookup for the existing entry
  875. // Anyhow, this loop is quite quick to execute because in the delta
  876. // the number of nodes is limited
  877. $oNodeClone = null;
  878. foreach ($oParentClone->childNodes as $oChild)
  879. {
  880. if (($oChild instanceof DOMElement) && ($oChild->tagName == $oNode->tagName))
  881. {
  882. if (!$oNode->hasAttribute('id') || ($oNode->getAttribute('id') == $oChild->getAttribute('id')))
  883. {
  884. $oNodeClone = $oChild;
  885. break;
  886. }
  887. }
  888. }
  889. if (!$oNodeClone)
  890. {
  891. $bCopyContents = ($sAlteration == 'replaced') || ($sAlteration == 'added') || ($sAlteration == 'needed');
  892. $oNodeClone = $oTargetDoc->importNode($oNode->cloneNode($bCopyContents), $bCopyContents);
  893. $this->SetDeltaFlags($oNodeClone);
  894. $oParentClone->appendChild($oNodeClone);
  895. }
  896. }
  897. return $oNodeClone;
  898. }
  899. /**
  900. * Set the value for a given trace attribute
  901. * See MFElement::SetTrace to enable/disable change traces
  902. */
  903. public function SetTraceValue($sAttribute, $sPreviousValue, $sNewValue)
  904. {
  905. // Search into the deleted node as well!
  906. $oNodeSet = $this->oDOMDocument->GetNodes("//*[@$sAttribute='$sPreviousValue']", null, false);
  907. foreach($oNodeSet as $oTouchedNode)
  908. {
  909. $oTouchedNode->setAttribute($sAttribute, $sNewValue);
  910. }
  911. }
  912. /**
  913. * Get the document version of the delta
  914. */
  915. public function GetDeltaDocument($aNodesToIgnore = array(), $aAttributes = null)
  916. {
  917. $oDelta = new MFDocument();
  918. // Handle classes moved from one parent to another
  919. // This will be done in two steps:
  920. // 1) Identify the moved classes (marked as deleted under the original parent, and created under the new parent)
  921. // 2) When importing those "moved" classes into the delta (see ImportNodeAndPathDelta), extract them from the hierarchy (the alteration can be done at an upper level in the hierarchy) and mark them as "modified"
  922. $aMovedClasses = array();
  923. foreach($this->GetNodes("/itop_design/classes//class/class[@_alteration='removed']", null, false) as $oNode)
  924. {
  925. $sId = $oNode->getAttribute('id');
  926. if ($this->GetNodes("/itop_design/classes//class/class[@id='$sId']/properties", null, false)->length > 0)
  927. {
  928. $aMovedClasses[$sId] = true;
  929. }
  930. }
  931. foreach($this->ListChanges() as $oAlteredNode)
  932. {
  933. $this->ImportNodeAndPathDelta($aMovedClasses, $oDelta, $oAlteredNode);
  934. }
  935. foreach($aNodesToIgnore as $sXPath)
  936. {
  937. $oNodesToRemove = $oDelta->GetNodes($sXPath);
  938. foreach($oNodesToRemove as $oNode)
  939. {
  940. if ($oNode instanceof DOMAttr)
  941. {
  942. $oNode->ownerElement->removeAttributeNode($oNode);
  943. }
  944. else
  945. {
  946. $oNode->parentNode->removeChild($oNode);
  947. }
  948. }
  949. }
  950. if ($aAttributes != null)
  951. {
  952. foreach ($aAttributes as $sAttribute => $value)
  953. {
  954. $oDelta->documentElement->setAttribute($sAttribute, $value);
  955. }
  956. }
  957. return $oDelta;
  958. }
  959. /**
  960. * Get the text/XML version of the delta
  961. */
  962. public function GetDelta($aNodesToIgnore = array(), $aAttributes = null)
  963. {
  964. $oDelta = $this->GetDeltaDocument($aNodesToIgnore, $aAttributes);
  965. return $oDelta->saveXML();
  966. }
  967. /**
  968. * Searches on disk in the root directories for module description files
  969. * and returns an array of MFModules
  970. * @return array Array of MFModules
  971. */
  972. public function FindModules()
  973. {
  974. $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->aRootDirs);
  975. $aResult = array();
  976. foreach($aAvailableModules as $sId => $aModule)
  977. {
  978. $aResult[] = new MFModule($sId, $aModule['root_dir'], $aModule['label']);
  979. }
  980. return $aResult;
  981. }
  982. public function TestAlteration()
  983. {
  984. try
  985. {
  986. $sHeader = '<?xml version="1.0" encoding="utf-8"?'.'>';
  987. $sOriginalXML =
  988. <<<EOF
  989. $sHeader
  990. <itop_design>
  991. <a id="first a">
  992. <b>Text</b>
  993. <c id="1">
  994. <d>D1</d>
  995. <d>D2</d>
  996. </c>
  997. </a>
  998. <a id="second a">
  999. <parent>first a</parent>
  1000. </a>
  1001. <a id="third a">
  1002. <parent>first a</parent>
  1003. <x>blah</x>
  1004. </a>
  1005. </itop_design>
  1006. EOF;
  1007. $this->oDOMDocument = new MFDocument();
  1008. $this->oDOMDocument->loadXML($sOriginalXML);
  1009. // DOM Get the original values, then modify its contents by the mean of the API
  1010. $oRoot = $this->GetNodes('//itop_design')->item(0);
  1011. //$oRoot->Dump();
  1012. $sDOMOriginal = $oRoot->Dump(true);
  1013. $oNode = $oRoot->GetNodes('a/b')->item(0);
  1014. $oNew = $this->oDOMDocument->CreateElement('b', 'New text');
  1015. $oNode->parentNode->RedefineChildNode($oNew);
  1016. $oNode = $oRoot->GetNodes('a/c')->item(0);
  1017. $oNewC = $this->oDOMDocument->CreateElement('c');
  1018. $oNewC->setAttribute('id', '1');
  1019. $oNode->parentNode->RedefineChildNode($oNewC);
  1020. $oNewC->appendChild($this->oDOMDocument->CreateElement('d', 'x'));
  1021. $oNewC->appendChild($this->oDOMDocument->CreateElement('d', 'y'));
  1022. $oNewC->appendChild($this->oDOMDocument->CreateElement('d', 'z'));
  1023. $oNamedNode = $this->oDOMDocument->CreateElement('z');
  1024. $oNamedNode->setAttribute('id', 'abc');
  1025. $oNewC->AddChildNode($oNamedNode);
  1026. $oNewC->AddChildNode($this->oDOMDocument->CreateElement('r', 'to be replaced'));
  1027. // Alter this "modified node", no flag should be set in its subnodes
  1028. $oNewC->Rename('blah');
  1029. $oNewC->Rename('foo');
  1030. $oNewC->AddChildNode($this->oDOMDocument->CreateElement('y', '(no flag)'));
  1031. $oNewC->AddChildNode($this->oDOMDocument->CreateElement('x', 'To delete programatically'));
  1032. $oSubNode = $oNewC->GetUniqueElement('z');
  1033. $oSubNode->Rename('abcdef');
  1034. $oSubNode = $oNewC->GetUniqueElement('x');
  1035. $oSubNode->Delete();
  1036. $oNewC->RedefineChildNode($this->oDOMDocument->CreateElement('r', 'replacement'));
  1037. $oNode = $oRoot->GetNodes("//a[@id='second a']")->item(0);
  1038. $oNode->Rename('el 2o A');
  1039. $oNode->Rename('el secundo A');
  1040. $oNew = $this->oDOMDocument->CreateElement('e', 'Something new here');
  1041. $oNode->AddChildNode($oNew);
  1042. $oNewA = $this->oDOMDocument->CreateElement('a');
  1043. $oNewA->setAttribute('id', 'new a');
  1044. $oNode->AddChildNode($oNewA);
  1045. $oSubnode = $this->oDOMDocument->CreateElement('parent', 'el secundo A');
  1046. $oSubnode->setAttribute('id', 'to be changed');
  1047. $oNewA->AddChildNode($oSubnode);
  1048. $oNewA->AddChildNode($this->oDOMDocument->CreateElement('f', 'Welcome to the newcomer'));
  1049. $oNewA->AddChildNode($this->oDOMDocument->CreateElement('x', 'To delete programatically'));
  1050. // Alter this "new a", as it is new, no flag should be set
  1051. $oNewA->Rename('new_a');
  1052. $oSubNode = $oNewA->GetUniqueElement('parent');
  1053. $oSubNode->Rename('alter ego');
  1054. $oNewA->RedefineChildNode($this->oDOMDocument->CreateElement('f', 'dummy data'));
  1055. $oSubNode = $oNewA->GetUniqueElement('x');
  1056. $oSubNode->Delete();
  1057. $oNode = $oRoot->GetNodes("//a[@id='third a']")->item(0);
  1058. $oNode->Delete();
  1059. //$oRoot->Dump();
  1060. $sDOMModified = $oRoot->Dump(true);
  1061. // Compute the delta
  1062. //
  1063. $sDeltaXML = $this->GetDelta();
  1064. //echo "<pre>\n";
  1065. //echo htmlentities($sDeltaXML);
  1066. //echo "</pre>\n";
  1067. // Reiterating - try to remake the DOM by applying the computed delta
  1068. //
  1069. $this->oDOMDocument = new MFDocument();
  1070. $this->oDOMDocument->loadXML($sOriginalXML);
  1071. $oRoot = $this->GetNodes('//itop_design')->item(0);
  1072. //$oRoot->Dump();
  1073. echo "<h4>Rebuild the DOM - Delta applied...</h4>\n";
  1074. $oDeltaDoc = new MFDocument();
  1075. $oDeltaDoc->loadXML($sDeltaXML);
  1076. //$oDeltaDoc->Dump();
  1077. //$this->oDOMDocument->Dump();
  1078. $oDeltaRoot = $oDeltaDoc->childNodes->item(0);
  1079. $this->LoadDelta($oDeltaRoot, $this->oDOMDocument);
  1080. //$oRoot->Dump();
  1081. $sDOMRebuilt = $oRoot->Dump(true);
  1082. }
  1083. catch (Exception $e)
  1084. {
  1085. echo "<h1>Exception: ".$e->getMessage()."</h1>\n";
  1086. echo "<pre>\n";
  1087. debug_print_backtrace();
  1088. echo "</pre>\n";
  1089. }
  1090. $sArrStyle = "font-size: 40;";
  1091. echo "<table>\n";
  1092. echo " <tr>\n";
  1093. echo " <td width=\"50%\">\n";
  1094. echo " <h4>DOM - Original values</h4>\n";
  1095. echo " <pre>".htmlentities($sDOMOriginal)."</pre>\n";
  1096. echo " </td>\n";
  1097. echo " <td width=\"50%\" align=\"left\" valign=\"center\"><span style=\"$sArrStyle\">&rArr; &rArr; &rArr;</span></td>\n";
  1098. echo " </tr>\n";
  1099. echo " <tr><td align=\"center\"><span style=\"$sArrStyle\">&dArr;</div></td><td align=\"center\"><span style=\"$sArrStyle\"><span style=\"$sArrStyle\">&dArr;</div></div></td></tr>\n";
  1100. echo " <tr>\n";
  1101. echo " <td width=\"50%\">\n";
  1102. echo " <h4>DOM - Altered with various changes</h4>\n";
  1103. echo " <pre>".htmlentities($sDOMModified)."</pre>\n";
  1104. echo " </td>\n";
  1105. echo " <td width=\"50%\">\n";
  1106. echo " <h4>DOM - Rebuilt from the Delta</h4>\n";
  1107. echo " <pre>".htmlentities($sDOMRebuilt)."</pre>\n";
  1108. echo " </td>\n";
  1109. echo " </tr>\n";
  1110. echo " <tr><td align=\"center\"><span style=\"$sArrStyle\">&dArr;</div></td><td align=\"center\"><span style=\"$sArrStyle\">&uArr;</div></td></tr>\n";
  1111. echo " <td width=\"50%\">\n";
  1112. echo " <h4>Delta (Computed by ModelFactory)</h4>\n";
  1113. echo " <pre>".htmlentities($sDeltaXML)."</pre>\n";
  1114. echo " </td>\n";
  1115. echo " <td width=\"50%\" align=\"left\" valign=\"center\"><span style=\"$sArrStyle\">&rArr; &rArr; &rArr;</span></td>\n";
  1116. echo " </tr>\n";
  1117. echo "</table>\n";
  1118. } // TEST !
  1119. /**
  1120. * Extracts some nodes from the DOM
  1121. * @param string $sXPath A XPath expression
  1122. * @return DOMNodeList
  1123. */
  1124. public function GetNodes($sXPath, $oContextNode = null, $bSafe = true)
  1125. {
  1126. return $this->oDOMDocument->GetNodes($sXPath, $oContextNode, $bSafe);
  1127. }
  1128. }
  1129. /**
  1130. * Allow the setup page to load and perform its checks (including the check about the required extensions)
  1131. */
  1132. if (!class_exists('DOMElement'))
  1133. {
  1134. class DOMElement {function __construct(){throw new Exception('The dom extension is not enabled');}}
  1135. }
  1136. /**
  1137. * MFElement: helper to read/change the DOM
  1138. * @package ModelFactory
  1139. */
  1140. class MFElement extends DOMElement
  1141. {
  1142. /**
  1143. * Extracts some nodes from the DOM
  1144. * @param string $sXPath A XPath expression
  1145. * @return DOMNodeList
  1146. */
  1147. public function GetNodes($sXPath, $bSafe = true)
  1148. {
  1149. return $this->ownerDocument->GetNodes($sXPath, $this, $bSafe);
  1150. }
  1151. /**
  1152. * Extracts some nodes from the DOM (active nodes only !!!)
  1153. * @param string $sXPath A XPath expression
  1154. * @return DOMNodeList
  1155. */
  1156. public function GetNodeById($sXPath, $sId)
  1157. {
  1158. return $this->ownerDocument->GetNodeById($sXPath, $sId, $this);
  1159. }
  1160. /**
  1161. * For debugging purposes
  1162. */
  1163. public function Dump($bReturnRes = false)
  1164. {
  1165. $oMFDoc = new MFDocument();
  1166. $oClone = $oMFDoc->importNode($this->cloneNode(true), true);
  1167. $oMFDoc->appendChild($oClone);
  1168. $sXml = $oMFDoc->saveXML($oClone);
  1169. if ($bReturnRes)
  1170. {
  1171. return $sXml;
  1172. }
  1173. else
  1174. {
  1175. echo "<pre>\n";
  1176. echo htmlentities($sXml);
  1177. echo "</pre>\n";
  1178. }
  1179. }
  1180. /**
  1181. * Returns the node directly under the given node
  1182. */
  1183. public function GetUniqueElement($sTagName, $bMustExist = true)
  1184. {
  1185. $oNode = null;
  1186. foreach($this->childNodes as $oChildNode)
  1187. {
  1188. if (($oChildNode->nodeName == $sTagName) && (($oChildNode->getAttribute('_alteration') != 'removed')))
  1189. {
  1190. $oNode = $oChildNode;
  1191. break;
  1192. }
  1193. }
  1194. if ($bMustExist && is_null($oNode))
  1195. {
  1196. throw new DOMFormatException('Missing unique tag: '.$sTagName);
  1197. }
  1198. return $oNode;
  1199. }
  1200. /**
  1201. * Returns the node directly under the current node, or null if missing
  1202. */
  1203. public function GetOptionalElement($sTagName)
  1204. {
  1205. return $this->GetUniqueElement($sTagName, false);
  1206. }
  1207. /**
  1208. * Returns the TEXT of the current node (possibly from several subnodes)
  1209. */
  1210. public function GetText($sDefault = null)
  1211. {
  1212. $sText = null;
  1213. foreach($this->childNodes as $oChildNode)
  1214. {
  1215. if ($oChildNode instanceof DOMCharacterData) // Base class of DOMText and DOMCdataSection
  1216. {
  1217. if (is_null($sText)) $sText = '';
  1218. $sText .= $oChildNode->wholeText;
  1219. }
  1220. }
  1221. if (is_null($sText))
  1222. {
  1223. return $sDefault;
  1224. }
  1225. else
  1226. {
  1227. return $sText;
  1228. }
  1229. }
  1230. /**
  1231. * Get the TEXT value from the child node
  1232. */
  1233. public function GetChildText($sTagName, $sDefault = null)
  1234. {
  1235. $sRet = $sDefault;
  1236. if ($oChild = $this->GetOptionalElement($sTagName))
  1237. {
  1238. $sRet = $oChild->GetText($sDefault);
  1239. }
  1240. return $sRet;
  1241. }
  1242. /**
  1243. * Assumes the current node to be either a text or
  1244. * <items>
  1245. * <item [key]="..."]>value<item>
  1246. * <item [key]="..."]>value<item>
  1247. * </items>
  1248. * where value can be the either a text or an array of items... recursively
  1249. * Returns a PHP array
  1250. */
  1251. public function GetNodeAsArrayOfItems($sElementName = 'items')
  1252. {
  1253. $oItems = $this->GetOptionalElement($sElementName);
  1254. if ($oItems)
  1255. {
  1256. $res = array();
  1257. $aRanks = array();
  1258. foreach($oItems->childNodes as $oItem)
  1259. {
  1260. if ($oItem instanceof DOMElement)
  1261. {
  1262. // When an attribute is missing
  1263. if ($oItem->hasAttribute('id'))
  1264. {
  1265. $key = $oItem->getAttribute('id');
  1266. if (array_key_exists($key, $res))
  1267. {
  1268. // Houston!
  1269. throw new DOMFormatException("Tag ".$oItem->getNodePath().", id '$key' already used!!!");
  1270. }
  1271. $res[$key] = $oItem->GetNodeAsArrayOfItems();
  1272. }
  1273. else
  1274. {
  1275. $res[] = $oItem->GetNodeAsArrayOfItems();
  1276. }
  1277. $sRank = $oItem->GetChildText('rank');
  1278. if ($sRank != '')
  1279. {
  1280. $aRanks[] = (float) $sRank;
  1281. }
  1282. else
  1283. {
  1284. $aRanks[] = count($aRanks) > 0 ? max($aRanks) + 1 : 0;
  1285. }
  1286. array_multisort($aRanks, $res);
  1287. }
  1288. }
  1289. }
  1290. else
  1291. {
  1292. $res = $this->GetText();
  1293. }
  1294. return $res;
  1295. }
  1296. public function SetNodeAsArrayOfItems($aList)
  1297. {
  1298. $oNewNode = $this->ownerDocument->CreateElement($this->tagName);
  1299. if ($this->getAttribute('id') != '')
  1300. {
  1301. $oNewNode->setAttribute('id', $this->getAttribute('id'));
  1302. }
  1303. self::AddItemToNode($this->ownerDocument, $oNewNode, $aList);
  1304. $this->parentNode->RedefineChildNode($oNewNode);
  1305. }
  1306. protected static function AddItemToNode($oXmlDoc, $oXMLNode, $itemValue)
  1307. {
  1308. if (is_array($itemValue))
  1309. {
  1310. $oXmlItems = $oXmlDoc->CreateElement('items');
  1311. $oXMLNode->AppendChild($oXmlItems);
  1312. foreach($itemValue as $key => $item)
  1313. {
  1314. $oXmlItem = $oXmlDoc->CreateElement('item');
  1315. $oXmlItems->AppendChild($oXmlItem);
  1316. if (is_string($key))
  1317. {
  1318. $oXmlItem->SetAttribute('key', $key);
  1319. }
  1320. self::AddItemToNode($oXmlDoc, $oXmlItem, $item);
  1321. }
  1322. }
  1323. else
  1324. {
  1325. $oXmlText = $oXmlDoc->CreateTextNode((string) $itemValue);
  1326. $oXMLNode->AppendChild($oXmlText);
  1327. }
  1328. }
  1329. /**
  1330. * Helper to remove child nodes
  1331. */
  1332. protected function DeleteChildren()
  1333. {
  1334. while (isset($this->firstChild))
  1335. {
  1336. if ($this->firstChild instanceof MFElement)
  1337. {
  1338. $this->firstChild->DeleteChildren();
  1339. }
  1340. $this->removeChild($this->firstChild);
  1341. }
  1342. }
  1343. /**
  1344. * Find the child node matching the given node.
  1345. * UNSAFE: may return nodes marked as _alteration="removed"
  1346. * A method with the same signature MUST exist in MFDocument for the recursion to work fine
  1347. * @param MFElement $oRefNode The node to search for
  1348. * @param string $sSearchId substitutes to the value of the 'id' attribute
  1349. */
  1350. public function _FindChildNode(MFElement $oRefNode, $sSearchId = null)
  1351. {
  1352. return self::_FindNode($this, $oRefNode, $sSearchId);
  1353. }
  1354. /**
  1355. * Find the child node matching the given node under the specified parent.
  1356. * UNSAFE: may return nodes marked as _alteration="removed"
  1357. * @param DOMNode $oParent
  1358. * @param MFElement $oRefNode
  1359. * @param string $sSearchId
  1360. * @throws Exception
  1361. */
  1362. public static function _FindNode(DOMNode $oParent, MFElement $oRefNode, $sSearchId = null)
  1363. {
  1364. $oRes = null;
  1365. if ($oParent instanceof DOMDocument)
  1366. {
  1367. $oDoc = $oParent->firstChild->ownerDocument;
  1368. $oRoot = $oParent;
  1369. }
  1370. else
  1371. {
  1372. $oDoc = $oParent->ownerDocument;
  1373. $oRoot = $oParent;
  1374. }
  1375. $oXPath = new DOMXPath($oDoc);
  1376. if ($oRefNode->hasAttribute('id'))
  1377. {
  1378. // Find the first element having the same tag name and id
  1379. if (!$sSearchId)
  1380. {
  1381. $sSearchId = $oRefNode->getAttribute('id');
  1382. }
  1383. $sXPath = './'.$oRefNode->tagName."[@id='$sSearchId']";
  1384. $oRes = $oXPath->query($sXPath, $oRoot)->item(0);
  1385. }
  1386. else
  1387. {
  1388. // Get the first one having the same tag name (ignore others)
  1389. $sXPath = './'.$oRefNode->tagName;
  1390. $oRes = $oXPath->query($sXPath, $oRoot)->item(0);
  1391. }
  1392. return $oRes;
  1393. }
  1394. /**
  1395. * Check if the current node is under a node 'added' or 'altered'
  1396. * Usage: In such a case, the change must not be tracked
  1397. */
  1398. public function IsInDefinition()
  1399. {
  1400. // Iterate through the parents: reset the flag if any of them has a flag set
  1401. for($oParent = $this ; $oParent instanceof MFElement ; $oParent = $oParent->parentNode)
  1402. {
  1403. if ($oParent->getAttribute('_alteration') != '')
  1404. {
  1405. return true;
  1406. }
  1407. }
  1408. return false;
  1409. }
  1410. static $aTraceAttributes = null;
  1411. /**
  1412. * Enable/disable the trace on changed nodes
  1413. *
  1414. *@param aAttributes array Array of attributes (key => value) to be added onto any changed node
  1415. */
  1416. static public function SetTrace($aAttributes = null)
  1417. {
  1418. self::$aTraceAttributes = $aAttributes;
  1419. }
  1420. /**
  1421. * Mark the node as touched (if tracing is active)
  1422. */
  1423. public function AddTrace()
  1424. {
  1425. if (!is_null(self::$aTraceAttributes))
  1426. {
  1427. foreach (self::$aTraceAttributes as $sAttribute => $value)
  1428. {
  1429. $this->setAttribute($sAttribute, $value);
  1430. }
  1431. }
  1432. }
  1433. /**
  1434. * Add a node and set the flags that will be used to compute the delta
  1435. * @param MFElement $oNode The node (including all subnodes) to add
  1436. */
  1437. public function AddChildNode(MFElement $oNode)
  1438. {
  1439. // First: cleanup any flag behind the new node, and eventually add trace data
  1440. $oNode->ApplyChanges();
  1441. $oNode->AddTrace();
  1442. $oExisting = $this->_FindChildNode($oNode);
  1443. if ($oExisting)
  1444. {
  1445. if ($oExisting->getAttribute('_alteration') != 'removed')
  1446. {
  1447. throw new Exception("Attempting to add a node that already exists: $oNode->tagName (id: ".$oNode->getAttribute('id').")");
  1448. }
  1449. $oExisting->ReplaceWith($oNode);
  1450. $sFlag = 'replaced';
  1451. }
  1452. else
  1453. {
  1454. $this->appendChild($oNode);
  1455. $sFlag = 'added';
  1456. }
  1457. if (!$this->IsInDefinition())
  1458. {
  1459. $oNode->setAttribute('_alteration', $sFlag);
  1460. }
  1461. }
  1462. /**
  1463. * Modify a node and set the flags that will be used to compute the delta
  1464. * @param MFElement $oNode The node (including all subnodes) to set
  1465. */
  1466. public function RedefineChildNode(MFElement $oNode, $sSearchId = null)
  1467. {
  1468. // First: cleanup any flag behind the new node, and eventually add trace data
  1469. $oNode->ApplyChanges();
  1470. $oNode->AddTrace();
  1471. $oExisting = $this->_FindChildNode($oNode, $sSearchId);
  1472. if (!$oExisting)
  1473. {
  1474. throw new Exception("Attempting to modify a non existing node: $oNode->tagName (id: ".$oNode->getAttribute('id').")");
  1475. }
  1476. $sPrevFlag = $oExisting->getAttribute('_alteration');
  1477. if ($sPrevFlag == 'removed')
  1478. {
  1479. throw new Exception("Attempting to modify a deleted node: $oNode->tagName (id: ".$oNode->getAttribute('id')."");
  1480. }
  1481. $oExisting->ReplaceWith($oNode);
  1482. if (!$this->IsInDefinition())
  1483. {
  1484. if ($sPrevFlag == '')
  1485. {
  1486. $sPrevFlag = 'replaced';
  1487. }
  1488. $oNode->setAttribute('_alteration', $sPrevFlag);
  1489. }
  1490. }
  1491. /**
  1492. * Combination of AddChildNode or RedefineChildNode... it depends
  1493. * This should become the preferred way of doing things (instead of implementing a test + the call to one of the APIs!
  1494. * @param MFElement $oNode The node (including all subnodes) to set
  1495. */
  1496. public function SetChildNode(MFElement $oNode, $sSearchId = null)
  1497. {
  1498. // First: cleanup any flag behind the new node, and eventually add trace data
  1499. $oNode->ApplyChanges();
  1500. $oNode->AddTrace();
  1501. $oExisting = $this->_FindChildNode($oNode, $sSearchId);
  1502. if ($oExisting)
  1503. {
  1504. $sPrevFlag = $oExisting->getAttribute('_alteration');
  1505. if ($sPrevFlag == 'removed')
  1506. {
  1507. $sFlag = 'replaced';
  1508. }
  1509. else
  1510. {
  1511. $sFlag = $sPrevFlag; // added, replaced or ''
  1512. }
  1513. $oExisting->ReplaceWith($oNode);
  1514. }
  1515. else
  1516. {
  1517. $this->appendChild($oNode);
  1518. $sFlag = 'added';
  1519. }
  1520. if (!$this->IsInDefinition())
  1521. {
  1522. if ($sFlag == '')
  1523. {
  1524. $sFlag = 'replaced';
  1525. }
  1526. $oNode->setAttribute('_alteration', $sFlag);
  1527. }
  1528. }
  1529. /**
  1530. * Check that the current node is actually a class node, under classes
  1531. */
  1532. public function IsClassNode()
  1533. {
  1534. if ($this->tagName == 'class')
  1535. {
  1536. if (($this->parentNode->tagName == 'classes') && ($this->parentNode->parentNode->tagName == 'itop_design') ) // Beware: classes/class also exists in the group definition
  1537. {
  1538. return true;
  1539. }
  1540. return $this->parentNode->IsClassNode();
  1541. }
  1542. else
  1543. {
  1544. return false;
  1545. }
  1546. }
  1547. /**
  1548. * Replaces a node by another one, making sure that recursive nodes are preserved
  1549. * @param MFElement $oNewNode The replacement
  1550. */
  1551. protected function ReplaceWith($oNewNode)
  1552. {
  1553. // Move the classes from the old node into the new one
  1554. if ($this->IsClassNode())
  1555. {
  1556. foreach($this->GetNodes('class') as $oChild)
  1557. {
  1558. $oNewNode->appendChild($oChild);
  1559. }
  1560. }
  1561. $oParentNode = $this->parentNode;
  1562. $oParentNode->replaceChild($oNewNode, $this);
  1563. }
  1564. /**
  1565. * Remove a node and set the flags that will be used to compute the delta
  1566. */
  1567. public function Delete()
  1568. {
  1569. $oParent = $this->parentNode;
  1570. switch ($this->getAttribute('_alteration'))
  1571. {
  1572. case 'replaced':
  1573. $sFlag = 'removed';
  1574. break;
  1575. case 'added':
  1576. case 'needed':
  1577. $sFlag = null;
  1578. break;
  1579. case 'removed':
  1580. throw new Exception("Attempting to remove a deleted node: $this->tagName (id: ".$this->getAttribute('id')."");
  1581. default:
  1582. $sFlag = 'removed';
  1583. if ($this->IsInDefinition())
  1584. {
  1585. $sFlag = null;
  1586. break;
  1587. }
  1588. }
  1589. if ($sFlag)
  1590. {
  1591. $this->setAttribute('_alteration', $sFlag);
  1592. $this->DeleteChildren();
  1593. // Add trace data
  1594. $this->AddTrace();
  1595. }
  1596. else
  1597. {
  1598. // Remove the node entirely
  1599. $this->parentNode->removeChild($this);
  1600. }
  1601. }
  1602. /**
  1603. * Merge the current node into the given container
  1604. *
  1605. * @param DOMNode $oContainer An element or a document
  1606. * @param string $sSearchId The id to consider (could be blank)
  1607. * @param bool $bMustExist Throw an exception if the node must already be found (and not marked as deleted!)
  1608. */
  1609. public function MergeInto($oContainer, $sSearchId, $bMustExist)
  1610. {
  1611. $oTargetNode = $oContainer->_FindChildNode($this, $sSearchId);
  1612. if ($oTargetNode)
  1613. {
  1614. if ($oTargetNode->getAttribute('_alteration') == 'removed')
  1615. {
  1616. if ($bMustExist)
  1617. {
  1618. throw new Exception("found mandatory node $this->tagName(id:$sSearchId) marked as deleted in ".$oContainer->getNodePath());
  1619. }
  1620. // Beware: ImportNode(xxx, false) DOES NOT copy the node's attribute on *some* PHP versions (<5.2.17)
  1621. // So use this workaround to import a node and its attributes on *any* PHP version
  1622. $oTargetNode = $oContainer->ownerDocument->ImportNode($this->cloneNode(false), true);
  1623. $oContainer->AddChildNode($oTargetNode);
  1624. }
  1625. }
  1626. else
  1627. {
  1628. if ($bMustExist)
  1629. {
  1630. echo "Dumping parent node<br/>\n";
  1631. $oContainer->Dump();
  1632. throw new Exception("could not find $this->tagName(id:$sSearchId) in ".$oContainer->getNodePath());
  1633. }
  1634. // Beware: ImportNode(xxx, false) DOES NOT copy the node's attribute on *some* PHP versions (<5.2.17)
  1635. // So use this workaround to import a node and its attributes on *any* PHP version
  1636. $oTargetNode = $oContainer->ownerDocument->ImportNode($this->cloneNode(false), true);
  1637. $oContainer->AddChildNode($oTargetNode);
  1638. }
  1639. return $oTargetNode;
  1640. }
  1641. /**
  1642. * Renames a node and set the flags that will be used to compute the delta
  1643. * @param String $sNewId The new id
  1644. */
  1645. public function Rename($sId)
  1646. {
  1647. if (($this->getAttribute('_alteration') == 'replaced') || !$this->IsInDefinition())
  1648. {
  1649. $sOriginalId = $this->getAttribute('_old_id');
  1650. if ($sOriginalId == '')
  1651. {
  1652. $this->setAttribute('_old_id', $this->getAttribute('id'));
  1653. }
  1654. else if($sOriginalId == $sId)
  1655. {
  1656. $this->removeAttribute('_old_id');
  1657. }
  1658. }
  1659. $this->setAttribute('id', $sId);
  1660. // Leave a trace of this change
  1661. $this->AddTrace();
  1662. }
  1663. /**
  1664. * Apply extensibility rules onto this node
  1665. * @param array aRules Array of rules (strings)
  1666. * @return void
  1667. */
  1668. public function RestrictExtensibility($aRules)
  1669. {
  1670. $oRulesNode = $this->GetOptionalElement('rules');
  1671. if ($oRulesNode)
  1672. {
  1673. $aCurrentRules = $oRulesNode->GetNodeAsArrayOfItems();
  1674. $aCurrentRules = array_merge($aCurrentRules, $aRules);
  1675. $oRulesNode->SetNodeAsArrayOfItems($aCurrentRules);
  1676. }
  1677. else
  1678. {
  1679. $oNewNode = $this->ownerDocument->CreateElement('rules');
  1680. $this->appendChild($oNewNode);
  1681. $oNewNode->SetNodeAsArrayOfItems($aRules);
  1682. }
  1683. }
  1684. /**
  1685. * Read extensibility rules for this node
  1686. * @return Array of rules (strings)
  1687. */
  1688. public function GetExtensibilityRules()
  1689. {
  1690. $aCurrentRules = array();
  1691. $oRulesNode = $this->GetOptionalElement('rules');
  1692. if ($oRulesNode)
  1693. {
  1694. $aCurrentRules = $oRulesNode->GetNodeAsArrayOfItems();
  1695. }
  1696. return $aCurrentRules;
  1697. }
  1698. /**
  1699. * List changes below a given node (see also MFDocument::ListChanges)
  1700. */
  1701. public function ListChanges()
  1702. {
  1703. // Note: omitting the dot will make the query be global to the whole document!!!
  1704. return $this->ownerDocument->GetNodes('.//*[@_alteration or @_old_id]', $this, false);
  1705. }
  1706. /**
  1707. * List changes below a given node (see also MFDocument::ApplyChanges)
  1708. */
  1709. public function ApplyChanges()
  1710. {
  1711. $oNodes = $this->ListChanges();
  1712. foreach($oNodes as $oNode)
  1713. {
  1714. $sOperation = $oNode->GetAttribute('_alteration');
  1715. switch($sOperation)
  1716. {
  1717. case 'added':
  1718. case 'replaced':
  1719. case 'needed':
  1720. // marked as added or modified, just reset the flag
  1721. $oNode->removeAttribute('_alteration');
  1722. break;
  1723. case 'removed':
  1724. // marked as deleted, let's remove the node from the tree
  1725. $oNode->parentNode->removeChild($oNode);
  1726. break;
  1727. }
  1728. if ($oNode->hasAttribute('_old_id'))
  1729. {
  1730. $oNode->removeAttribute('_old_id');
  1731. }
  1732. }
  1733. }
  1734. }
  1735. /**
  1736. * Allow the setup page to load and perform its checks (including the check about the required extensions)
  1737. */
  1738. if (!class_exists('DOMDocument'))
  1739. {
  1740. class DOMDocument {function __construct(){throw new Exception('The dom extension is not enabled');}}
  1741. }
  1742. /**
  1743. * MFDocument - formating rules for XML input/output
  1744. * @package ModelFactory
  1745. */
  1746. class MFDocument extends DOMDocument
  1747. {
  1748. public function __construct()
  1749. {
  1750. parent::__construct('1.0', 'UTF-8');
  1751. $this->registerNodeClass('DOMElement', 'MFElement');
  1752. $this->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
  1753. $this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
  1754. }
  1755. /**
  1756. * Overload of the standard API
  1757. */
  1758. public function load($filename, $options = 0)
  1759. {
  1760. parent::load($filename, LIBXML_NOBLANKS);
  1761. }
  1762. /**
  1763. * Overload of the standard API
  1764. */
  1765. public function loadXML($source, $options = 0)
  1766. {
  1767. parent::loadXML($source, LIBXML_NOBLANKS);
  1768. }
  1769. /**
  1770. * Overload the standard API
  1771. */
  1772. public function saveXML(DOMNode $node = null, $options = 0)
  1773. {
  1774. $oRootNode = $this->firstChild;
  1775. if (!$oRootNode)
  1776. {
  1777. $oRootNode = $this->createElement('itop_design'); // make sure that the document is not empty
  1778. $oRootNode->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
  1779. $oRootNode->setAttribute('version', ITOP_DESIGN_LATEST_VERSION);
  1780. $this->appendChild($oRootNode);
  1781. }
  1782. return parent::saveXML();
  1783. }
  1784. /**
  1785. * For debugging purposes
  1786. */
  1787. public function Dump($bReturnRes = false)
  1788. {
  1789. $sXml = $this->saveXML();
  1790. if ($bReturnRes)
  1791. {
  1792. return $sXml;
  1793. }
  1794. else
  1795. {
  1796. echo "<pre>\n";
  1797. echo htmlentities($sXml);
  1798. echo "</pre>\n";
  1799. }
  1800. }
  1801. /**
  1802. * Find the child node matching the given node
  1803. * A method with the same signature MUST exist in MFElement for the recursion to work fine
  1804. * @param MFElement $oRefNode The node to search for
  1805. * @param string $sSearchId substitutes to the value of the 'id' attribute
  1806. */
  1807. public function _FindChildNode(MFElement $oRefNode, $sSearchId = null)
  1808. {
  1809. return MFElement::_FindNode($this, $oRefNode, $sSearchId);
  1810. }
  1811. /**
  1812. * Extracts some nodes from the DOM
  1813. * @param string $sXPath A XPath expression
  1814. * @return DOMNodeList
  1815. */
  1816. public function GetNodes($sXPath, $oContextNode = null, $bSafe = true)
  1817. {
  1818. $oXPath = new DOMXPath($this);
  1819. if ($bSafe)
  1820. {
  1821. $sXPath .= "[not(@_alteration) or @_alteration!='removed']";
  1822. }
  1823. if (is_null($oContextNode))
  1824. {
  1825. $oResult = $oXPath->query($sXPath);
  1826. }
  1827. else
  1828. {
  1829. $oResult = $oXPath->query($sXPath, $oContextNode);
  1830. }
  1831. return $oResult;
  1832. }
  1833. public function GetNodeById($sXPath, $sId, $oContextNode = null)
  1834. {
  1835. $oXPath = new DOMXPath($this);
  1836. $sQuotedId = self::XPathQuote($sId);
  1837. $sXPath .= "[@id=$sQuotedId and(not(@_alteration) or @_alteration!='removed')]";
  1838. if (is_null($oContextNode))
  1839. {
  1840. return $oXPath->query($sXPath);
  1841. }
  1842. else
  1843. {
  1844. return $oXPath->query($sXPath, $oContextNode);
  1845. }
  1846. }
  1847. public static function XPathQuote($sValue)
  1848. {
  1849. if (strpos($sValue, '"') !== false)
  1850. {
  1851. $aParts = explode('"', $sValue);
  1852. $sRet = 'concat("'.implode('", \'"\', "', $aParts).'")';
  1853. }
  1854. else
  1855. {
  1856. $sRet = '"'.$sValue.'"';
  1857. }
  1858. return $sRet;
  1859. }
  1860. /**
  1861. * An alternative to getNodePath, that gives the id of nodes instead of the position within the children
  1862. */
  1863. public static function GetItopNodePath($oNode)
  1864. {
  1865. if ($oNode instanceof DOMDocument) return '';
  1866. $sId = $oNode->getAttribute('id');
  1867. $sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
  1868. return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
  1869. }
  1870. }