modelfactory.class.inc.php 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544
  1. <?php
  2. // Copyright (C) 2011 Combodo SARL
  3. //
  4. /**
  5. * ModelFactory: in-memory manipulation of the XML MetaModel
  6. *
  7. * @author Erwan Taloc <erwan.taloc@combodo.com>
  8. * @author Romain Quetiez <romain.quetiez@combodo.com>
  9. * @author Denis Flaven <denis.flaven@combodo.com>
  10. * @license Combodo Private
  11. */
  12. require_once(APPROOT.'setup/moduleinstaller.class.inc.php');
  13. /**
  14. * ModelFactoryItem: an item managed by the ModuleFactory
  15. * @package ModelFactory
  16. */
  17. abstract class MFItem
  18. {
  19. public function __construct($sName, $sValue)
  20. {
  21. parent::__construct($sName, $sValue);
  22. }
  23. /**
  24. * List the source files for this item
  25. */
  26. public function ListSources()
  27. {
  28. }
  29. /**
  30. * List the rights/restrictions for this item
  31. */
  32. public function ListRights()
  33. {
  34. }
  35. }
  36. /**
  37. * ModelFactoryModule: the representation of a Module (i.e. element that can be selected during the setup)
  38. * @package ModelFactory
  39. */
  40. class MFModule extends MFItem
  41. {
  42. protected $sId;
  43. protected $sName;
  44. protected $sVersion;
  45. protected $sRootDir;
  46. protected $sLabel;
  47. protected $aDataModels;
  48. public function __construct($sId, $sRootDir, $sLabel)
  49. {
  50. $this->sId = $sId;
  51. list($this->sName, $this->sVersion) = ModuleDiscovery::GetModuleName($sId);
  52. if (strlen($this->sVersion) == 0)
  53. {
  54. $this->sVersion = '1.0.0';
  55. }
  56. $this->sRootDir = $sRootDir;
  57. $this->sLabel = $sLabel;
  58. $this->aDataModels = array();
  59. // Scan the module's root directory to find the datamodel(*).xml files
  60. if ($hDir = opendir($sRootDir))
  61. {
  62. // This is the correct way to loop over the directory. (according to the documentation)
  63. while (($sFile = readdir($hDir)) !== false)
  64. {
  65. if (preg_match('/^datamodel(.*)\.xml$/i', $sFile, $aMatches))
  66. {
  67. $this->aDataModels[] = $this->sRootDir.'/'.$aMatches[0];
  68. }
  69. }
  70. closedir($hDir);
  71. }
  72. }
  73. public function GetId()
  74. {
  75. return $this->sId;
  76. }
  77. public function GetName()
  78. {
  79. return $this->sName;
  80. }
  81. public function GetVersion()
  82. {
  83. return $this->sVersion;
  84. }
  85. public function GetLabel()
  86. {
  87. return $this->sLabel;
  88. }
  89. public function GetRootDir()
  90. {
  91. return $this->sRootDir;
  92. }
  93. public function GetModuleDir()
  94. {
  95. return basename($this->sRootDir);
  96. }
  97. public function GetDataModelFiles()
  98. {
  99. return $this->aDataModels;
  100. }
  101. /**
  102. * List all classes in this module
  103. */
  104. public function ListClasses()
  105. {
  106. return array();
  107. }
  108. }
  109. class MFWorkspace extends MFModule
  110. {
  111. public function __construct($sRootDir)
  112. {
  113. $this->sId = 'itop-workspace';
  114. $this->sName = 'workspace';
  115. $this->sVersion = '1.0';
  116. $this->sRootDir = $sRootDir;
  117. $this->sLabel = 'Workspace';
  118. $this->aDataModels = array();
  119. $this->aDataModels[] = $this->GetWorkspacePath();
  120. }
  121. public function GetWorkspacePath()
  122. {
  123. return $this->sRootDir.'/workspace.xml';
  124. }
  125. public function GetName()
  126. {
  127. return ''; // The workspace itself has no name so that objects created inside it retain their original module's name
  128. }
  129. }
  130. /**
  131. * ModelFactoryClass: the representation of a Class (i.e. a PHP class)
  132. * @package ModelFactory
  133. */
  134. class MFClass extends MFItem
  135. {
  136. /**
  137. * List all fields of this class
  138. */
  139. public function ListFields()
  140. {
  141. return array();
  142. }
  143. /**
  144. * List all methods of this class
  145. */
  146. public function ListMethods()
  147. {
  148. return array();
  149. }
  150. /**
  151. * Whether or not the class has a lifecycle
  152. * @return bool
  153. */
  154. public function HasLifeCycle()
  155. {
  156. return true; //TODO implement
  157. }
  158. /**
  159. * Returns the code of the attribute used to store the lifecycle state
  160. * @return string
  161. */
  162. public function GetLifeCycleAttCode()
  163. {
  164. if ($this->HasLifeCycle())
  165. {
  166. }
  167. return '';
  168. }
  169. /**
  170. * List all states of this class
  171. */
  172. public function ListStates()
  173. {
  174. return array();
  175. }
  176. /**
  177. * List all relations of this class
  178. */
  179. public function ListRelations()
  180. {
  181. return array();
  182. }
  183. /**
  184. * List all transitions of this class
  185. */
  186. public function ListTransitions()
  187. {
  188. return array();
  189. }
  190. }
  191. /**
  192. * ModelFactoryField: the representation of a Field (i.e. a property of a class)
  193. * @package ModelFactory
  194. */
  195. class MFField extends MFItem
  196. {
  197. }
  198. /**
  199. * ModelFactoryMethod: the representation of a Method (i.e. a method of a class)
  200. * @package ModelFactory
  201. */
  202. class MFMethod extends MFItem
  203. {
  204. }
  205. /**
  206. * ModelFactoryState: the representation of a state in the life cycle of the class
  207. * @package ModelFactory
  208. */
  209. class MFState extends MFItem
  210. {
  211. }
  212. /**
  213. * ModelFactoryRelation: the representation of a n:n relationship between two classes
  214. * @package ModelFactory
  215. */
  216. class MFRelation extends MFItem
  217. {
  218. }
  219. /**
  220. * ModelFactoryTransition: the representation of a transition between two states in the life cycle of the class
  221. * @package ModelFactory
  222. */
  223. class MFTransition extends MFItem
  224. {
  225. }
  226. /**
  227. * ModelFactory: the class that manages the in-memory representation of the XML MetaModel
  228. * @package ModelFactory
  229. */
  230. class ModelFactory
  231. {
  232. protected $sRootDir;
  233. protected $oDOMDocument;
  234. protected $oRoot;
  235. protected $oClasses;
  236. protected $oMenus;
  237. static protected $aLoadedClasses;
  238. static protected $aLoadedMenus;
  239. static protected $aWellKnownParents = array('DBObject', 'CMDBObject','cmdbAbstractObject');
  240. static protected $aLoadedModules;
  241. static protected $aLoadErrors;
  242. public function __construct($sRootDir)
  243. {
  244. $this->sRootDir = $sRootDir;
  245. $this->oDOMDocument = new DOMDocument('1.0', 'UTF-8');
  246. $this->oRoot = $this->oDOMDocument->CreateElement('itop_design');
  247. $this->oDOMDocument->AppendChild($this->oRoot);
  248. $this->oClasses = $this->oDOMDocument->CreateElement('classes');
  249. $this->oRoot->AppendChild($this->oClasses);
  250. $this->oMenus = $this->oDOMDocument->CreateElement('menus');
  251. $this->oRoot->AppendChild($this->oMenus);
  252. self::$aLoadedClasses = array();
  253. self::$aLoadedMenus = array();
  254. self::$aLoadedModules = array();
  255. self::$aLoadErrors = array();
  256. libxml_use_internal_errors(true);
  257. }
  258. public function Dump($oNode = null)
  259. {
  260. if (is_null($oNode))
  261. {
  262. $oNode = $this->oMenus;
  263. }
  264. echo htmlentities($this->oDOMDocument->saveXML($oNode));
  265. }
  266. /**
  267. * Loads the definitions corresponding to the given Module
  268. * @param MFModule $oModule
  269. */
  270. public function LoadModule(MFModule $oModule)
  271. {
  272. $aDataModels = $oModule->GetDataModelFiles();
  273. $sModuleName = $oModule->GetName();
  274. $aClasses = array();
  275. self::$aLoadedModules[] = $oModule;
  276. foreach($aDataModels as $sXmlFile)
  277. {
  278. $oDocument = new DOMDocument('1.0', 'UTF-8');
  279. libxml_clear_errors();
  280. $oDocument->load($sXmlFile, LIBXML_NOBLANKS);
  281. $bValidated = $oDocument->schemaValidate(APPROOT.'setup/itop_design.xsd');
  282. $aErrors = libxml_get_errors();
  283. if (count($aErrors) > 0)
  284. {
  285. self::$aLoadErrors[$sModuleName] = $aErrors;
  286. }
  287. $oXPath = new DOMXPath($oDocument);
  288. $oNodeList = $oXPath->query('//*');
  289. foreach($oNodeList as $oNode)
  290. {
  291. $oNode->SetAttribute('_source', $sXmlFile);
  292. }
  293. $oXPath = new DOMXPath($oDocument);
  294. $oNodeList = $oXPath->query('/itop_design/classes/class');
  295. foreach($oNodeList as $oNode)
  296. {
  297. if ($oNode->hasAttribute('parent'))
  298. {
  299. $sParentClass = $oNode->GetAttribute('parent');
  300. }
  301. else
  302. {
  303. $sParentClass = '';
  304. }
  305. $sClassName = $oNode->GetAttribute('name');
  306. $aClasses[$sClassName] = array('name' => $sClassName, 'parent' => $sParentClass, 'node' => $oNode);
  307. }
  308. // Menus - temporary for compiling ???
  309. $oNodeList = $oXPath->query('/itop_design/menus/menu');
  310. foreach($oNodeList as $oNode)
  311. {
  312. $this->AddMenu($oNode, $sModuleName);
  313. }
  314. }
  315. $index = 1;
  316. do
  317. {
  318. $bNothingLoaded = true;
  319. foreach($aClasses as $sClassName => $aClassData)
  320. {
  321. $sOperation = $aClassData['node']->getAttribute('_operation');
  322. switch($sOperation)
  323. {
  324. case 'added':
  325. case '':
  326. if (in_array($aClassData['parent'], self::$aWellKnownParents))
  327. {
  328. $this->AddClass($aClassData['node'], $sModuleName);
  329. unset($aClasses[$sClassName]);
  330. $bNothingLoaded = false;
  331. }
  332. else if ($this->ClassNameExists($aClassData['parent']))
  333. {
  334. $this->AddClass($aClassData['node'], $sModuleName);
  335. unset($aClasses[$sClassName]);
  336. $bNothingLoaded = false;
  337. }
  338. break;
  339. case 'removed':
  340. unset($aClasses[$sClassName]);
  341. $this->RemoveClass($sClassName);
  342. break;
  343. case 'modified':
  344. unset($aClasses[$sClassName]);
  345. $this->AlterClass($sClassName, $aClassData['node']);
  346. break;
  347. }
  348. }
  349. $index++;
  350. }
  351. while((count($aClasses)>0) && !$bNothingLoaded);
  352. // The remaining classes have no known parent, let's add them at the root
  353. foreach($aClasses as $sClassName => $aClassData)
  354. {
  355. $sOperation = $aClassData['node']->getAttribute('_operation');
  356. switch($sOperation)
  357. {
  358. case 'added':
  359. case '':
  360. // Add the class as a new root class
  361. $this->AddClass($aClassData['node'], $sModuleName);
  362. $oNewNode = $this->oDOMDocument->ImportNode($aClassData['node']);
  363. break;
  364. case 'removed':
  365. $this->RemoveClass($sClassName);
  366. break;
  367. case 'modified':
  368. //@@TODO Handle the modification of a class here
  369. $this->AlterClass($sClassName, $aClassData['node']);
  370. break;
  371. }
  372. }
  373. }
  374. /**
  375. * XML load errors (XML format and validation)
  376. */
  377. function HasLoadErrors()
  378. {
  379. return (count(self::$aLoadErrors) > 0);
  380. }
  381. function GetLoadErrors()
  382. {
  383. return self::$aLoadErrors;
  384. }
  385. function GetLoadedModules($bExcludeWorkspace = true)
  386. {
  387. if ($bExcludeWorkspace)
  388. {
  389. $aModules = array();
  390. foreach(self::$aLoadedModules as $oModule)
  391. {
  392. if (!$oModule instanceof MFWorkspace)
  393. {
  394. $aModules[] = $oModule;
  395. }
  396. }
  397. }
  398. else
  399. {
  400. $aModules = self::$aLoadedModules;
  401. }
  402. return $aModules;
  403. }
  404. function GetModule($sModuleName)
  405. {
  406. foreach(self::$aLoadedModules as $oModule)
  407. {
  408. if ($oModule->GetName() == $sModuleName) return $oModule;
  409. }
  410. return null;
  411. }
  412. /**
  413. * Check if the class specified by the given node already exists in the loaded DOM
  414. * @param DOMNode $oClassNode The node corresponding to the class to load
  415. * @throws Exception
  416. * @return bool True if the class exists, false otherwise
  417. */
  418. protected function ClassExists(DOMNode $oClassNode)
  419. {
  420. if ($oClassNode->hasAttribute('name'))
  421. {
  422. $sClassName = $oClassNode->GetAttribute('name');
  423. }
  424. else
  425. {
  426. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  427. }
  428. return (array_key_exists($sClassName, self::$aLoadedClasses));
  429. }
  430. /**
  431. * Check if the class specified by the given name already exists in the loaded DOM
  432. * @param string $sClassName The node corresponding to the class to load
  433. * @throws Exception
  434. * @return bool True if the class exists, false otherwise
  435. */
  436. protected function ClassNameExists($sClassName)
  437. {
  438. return (array_key_exists($sClassName, self::$aLoadedClasses));
  439. }
  440. /**
  441. * Add the given class to the DOM
  442. * @param DOMNode $oClassNode
  443. * @param string $sModuleName The name of the module in which this class is declared
  444. * @throws Exception
  445. */
  446. public function AddClass(DOMNode $oClassNode, $sModuleName)
  447. {
  448. if ($oClassNode->hasAttribute('name'))
  449. {
  450. $sClassName = $oClassNode->GetAttribute('name');
  451. }
  452. else
  453. {
  454. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  455. }
  456. if ($this->ClassExists($oClassNode))
  457. {
  458. throw new Exception("ModelFactory::AddClass: Cannot add the already existing class $sClassName");
  459. }
  460. $sParentClass = '';
  461. if ($oClassNode->hasAttribute('parent'))
  462. {
  463. $sParentClass = $oClassNode->GetAttribute('parent');
  464. }
  465. //echo "Adding class: $sClassName, parent: $sParentClass<br/>";
  466. if (!in_array($sParentClass, self::$aWellKnownParents) && $this->ClassNameExists($sParentClass))
  467. {
  468. // The class is a subclass of a class already loaded, add it under
  469. self::$aLoadedClasses[$sClassName] = $this->oDOMDocument->ImportNode($oClassNode, true /* bDeep */);
  470. self::$aLoadedClasses[$sClassName]->SetAttribute('_operation', 'added');
  471. if ($sModuleName != '')
  472. {
  473. self::$aLoadedClasses[$sClassName]->SetAttribute('_created_in', $sModuleName);
  474. }
  475. self::$aLoadedClasses[$sParentClass]->AppendChild(self::$aLoadedClasses[$sClassName]);
  476. $bNothingLoaded = false;
  477. }
  478. else if (in_array($sParentClass, self::$aWellKnownParents))
  479. {
  480. // Add the class as a new root class
  481. self::$aLoadedClasses[$sClassName] = $this->oDOMDocument->ImportNode($oClassNode, true /* bDeep */);
  482. self::$aLoadedClasses[$sClassName]->SetAttribute('_operation', 'added');
  483. if ($sModuleName != '')
  484. {
  485. self::$aLoadedClasses[$sClassName]->SetAttribute('_created_in', $sModuleName);
  486. }
  487. $this->oClasses->AppendChild(self::$aLoadedClasses[$sClassName]);
  488. }
  489. else
  490. {
  491. throw new Exception("ModelFactory::AddClass: Cannot add the class $sClassName, unknown parent class: $sParentClass");
  492. }
  493. }
  494. /**
  495. * Remove a class from the DOM
  496. * @param string $sClass
  497. * @throws Exception
  498. */
  499. public function RemoveClass($sClass)
  500. {
  501. if (!$this->ClassNameExists($sClass))
  502. {
  503. throw new Exception("ModelFactory::RemoveClass: Cannot remove the non existing class $sClass");
  504. }
  505. $oClassNode = self::$aLoadedClasses[$sClass];
  506. if ($oClassNode->getAttribute('_operation') == 'added')
  507. {
  508. $oClassNode->parentNode->RemoveChild($oClassNode);
  509. unset(self::$aLoadedClasses[$sClass]);
  510. }
  511. else
  512. {
  513. self::$aLoadedClasses[$sClass]->SetAttribute('_operation', 'removed');
  514. //TODO: also mark as removed the child classes
  515. }
  516. }
  517. /**
  518. * Modify a class within the DOM
  519. * @param string $sMenuId
  520. * @param DOMNode $oMenuNode
  521. * @throws Exception
  522. */
  523. public function AlterClass($sClassName, DOMNode $oClassNode)
  524. {
  525. $sOriginalName = $sClassName;
  526. if ($this->ClassNameExists($sClassName))
  527. {
  528. $oDestNode = self::$aLoadedClasses[$sClassName];
  529. }
  530. else
  531. {
  532. $sOriginalName = $oClassNode->getAttribute('_original_name');
  533. if ($this->ClassNameExists($sOriginalName))
  534. {
  535. // Class was renamed !
  536. $oDestNode = self::$aLoadedClasses[$sOriginalName];
  537. }
  538. else
  539. {
  540. throw new Exception("ModelFactory::AddClass: Cannot alter the non-existing class $sClassName / $sOriginalName");
  541. }
  542. }
  543. $this->_priv_AlterNode($oDestNode, $oClassNode);
  544. $sClassName = $oDestNode->getAttribute('name');
  545. if ($sOriginalName != $sClassName)
  546. {
  547. unset(self::$aLoadedClasses[$sOriginalName]);
  548. self::$aLoadedClasses[$sClassName] = $oDestNode;
  549. }
  550. $this->_priv_SetFlag($oDestNode, 'modified');
  551. }
  552. /**
  553. * Add the given menu to the DOM
  554. * @param DOMNode $oMenuNode
  555. * @param string $sModuleName The name of the module in which this class is declared
  556. * @throws Exception
  557. */
  558. public function AddMenu($oMenuNode, $sModuleName)
  559. {
  560. $sMenuId = $oMenuNode->GetAttribute('id');
  561. self::$aLoadedMenus[$sMenuId] = $this->oDOMDocument->ImportNode($oMenuNode, true /* bDeep */);
  562. self::$aLoadedMenus[$sMenuId]->SetAttribute('_operation', 'added');
  563. if ($sModuleName != '')
  564. {
  565. self::$aLoadedMenus[$sMenuId]->SetAttribute('_created_in', $sModuleName);
  566. }
  567. $this->oMenus->AppendChild(self::$aLoadedMenus[$sMenuId]);
  568. }
  569. /**
  570. * Remove a menu from the DOM
  571. * @param string $sMenuId
  572. * @throws Exception
  573. */
  574. public function RemoveMenu($sMenuId)
  575. {
  576. $oMenuNode = self::$aLoadedMenus[$sClass];
  577. if ($oMenuNode->getAttribute('_operation') == 'added')
  578. {
  579. $oMenuNode->parentNode->RemoveChild($oMenuNode);
  580. unset(self::$aLoadedMenus[$sMenuId]);
  581. }
  582. else
  583. {
  584. self::$aLoadedMenus[$sMenuId]->SetAttribute('_operation', 'removed');
  585. }
  586. }
  587. /**
  588. * Modify a menu within the DOM
  589. * @param string $sMenuId
  590. * @param DOMNode $oMenuNode
  591. * @throws Exception
  592. */
  593. public function AlterMenu($sMenuId, DOMNode $oMenuNode)
  594. {
  595. // Todo - implement... do we have to handle menu renaming ???
  596. }
  597. protected function _priv_AlterNode(DOMNode $oNode, DOMNode $oDeltaNode)
  598. {
  599. foreach ($oDeltaNode->attributes as $sName => $oAttrNode)
  600. {
  601. $sCurrentValue = $oNode->getAttribute($sName);
  602. $sNewValue = $oAttrNode->value;
  603. $oNode->setAttribute($sName, $oAttrNode->value);
  604. }
  605. $aSrcChildNodes = $oNode->childNodes;
  606. foreach($oDeltaNode->childNodes as $index => $oChildNode)
  607. {
  608. if (!$oChildNode instanceof DOMElement)
  609. {
  610. // Text or CData nodes are treated by position
  611. $sOperation = $oChildNode->parentNode->getAttribute('_operation');
  612. switch($sOperation)
  613. {
  614. case 'removed':
  615. // ???
  616. break;
  617. case 'modified':
  618. case 'replaced':
  619. case 'added':
  620. $oNewNode = $this->oDOMDocument->importNode($oChildNode);
  621. $oSrcChildNode = $aSrcChildNodes->item($index);
  622. if ($oSrcChildNode)
  623. {
  624. $oNode->replaceChild($oNewNode, $oSrcChildNode);
  625. }
  626. else
  627. {
  628. $oNode->appendChild($oNewNode);
  629. }
  630. break;
  631. case '':
  632. // Do nothing
  633. }
  634. }
  635. else
  636. {
  637. $sOperation = $oChildNode->getAttribute('_operation');
  638. $sPath = $oChildNode->tagName;
  639. $sName = $oChildNode->getAttribute('name');
  640. if ($sName != '')
  641. {
  642. $sPath .= "[@name='$sName']";
  643. }
  644. switch($sOperation)
  645. {
  646. case 'removed':
  647. $oToRemove = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  648. if ($oToRemove != null)
  649. {
  650. $this->_priv_SetFlag($oToRemove, 'removed');
  651. }
  652. break;
  653. case 'modified':
  654. $oToModify = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  655. if ($oToModify != null)
  656. {
  657. $this->_priv_AlterNode($oToModify, $oChildNode);
  658. }
  659. else
  660. {
  661. throw new Exception("Cannot modify the non-existing node '$sPath' in '".$oNode->getNodePath()."'");
  662. }
  663. break;
  664. case 'replaced':
  665. $oNewNode = $this->oDOMDocument->importNode($oChildNode, true); // Import the node and its child nodes
  666. $oToModify = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  667. $oNode->replaceChild($oNewNode, $oToModify);
  668. break;
  669. case 'added':
  670. $oNewNode = $this->oDOMDocument->importNode($oChildNode);
  671. $oNode->appendChild($oNewNode);
  672. $this->_priv_SetFlag($oNewNode, 'added');
  673. break;
  674. case '':
  675. // Do nothing
  676. }
  677. }
  678. }
  679. }
  680. public function GetClassXMLTemplate($sName, $sIcon)
  681. {
  682. return
  683. <<<EOF
  684. <?xml version="1.0" encoding="utf-8"?>
  685. <class name="$sName" parent="" db_table="" category="" abstract="" key_type="autoincrement" db_key_field="id" db_final_class_field="finalclass">
  686. <properties>
  687. <comment/>
  688. <naming format=""><attributes/></naming>
  689. <reconciliation><attributes/></reconciliation>
  690. <display_template/>
  691. <icon>$sIcon</icon>
  692. </properties>
  693. <fields/>
  694. <methods/>
  695. <presentation>
  696. <details><items/></details>
  697. <search><items/></search>
  698. <list><items/></list>
  699. </presentation>
  700. </class>
  701. EOF
  702. ;
  703. }
  704. /**
  705. * List all classes from the DOM, for a given module
  706. * @param string $sModuleNale
  707. * @param bool $bFlattenLayers
  708. * @throws Exception
  709. */
  710. public function ListClasses($sModuleName, $bFlattenLayers = true)
  711. {
  712. $sXPath = "//class[@_created_in='$sModuleName']";
  713. if ($bFlattenLayers)
  714. {
  715. $sXPath = "//class[@_created_in='$sModuleName' and @_operation!='removed']";
  716. }
  717. return $this->_priv_GetNodes($sXPath);
  718. }
  719. /**
  720. * List all classes from the DOM, for a given module
  721. * @param string $sModuleNale
  722. * @param bool $bFlattenLayers
  723. * @throws Exception
  724. */
  725. public function ListAllClasses($bFlattenLayers = true)
  726. {
  727. $sXPath = "//class";
  728. if ($bFlattenLayers)
  729. {
  730. $sXPath = "//class[@_operation!='removed']";
  731. }
  732. return $this->_priv_GetNodes($sXPath);
  733. }
  734. public function GetClass($sClassName, $bFlattenLayers = true)
  735. {
  736. if (!$this->ClassNameExists($sClassName))
  737. {
  738. return null;
  739. }
  740. $oClassNode = self::$aLoadedClasses[$sClassName];
  741. if ($bFlattenLayers)
  742. {
  743. $sOperation = $oClassNode->getAttribute('_operation');
  744. if ($sOperation == 'removed')
  745. {
  746. $oClassNode = null;
  747. }
  748. }
  749. return $oClassNode;
  750. }
  751. public function GetChildClasses($oClassNode, $bFlattenLayers = true)
  752. {
  753. $sXPath = "class";
  754. if ($bFlattenLayers)
  755. {
  756. $sXPath = "class[(@_operation!='removed')]";
  757. }
  758. return $this->_priv_GetNodes($sXPath, $oClassNode);
  759. }
  760. public function GetField($sClassName, $sAttCode, $bFlattenLayers = true)
  761. {
  762. if (!$this->ClassNameExists($sClassName))
  763. {
  764. return null;
  765. }
  766. $oClassNode = self::$aLoadedClasses[$sClassName];
  767. if ($bFlattenLayers)
  768. {
  769. $sOperation = $oClassNode->getAttribute('_operation');
  770. if ($sOperation == 'removed')
  771. {
  772. $oClassNode = null;
  773. }
  774. }
  775. $sXPath = "fields/field[@name='$sAttCode']";
  776. if ($bFlattenLayers)
  777. {
  778. $sXPath = "fields/field[(@name='$sAttCode' and (not(@_operation) or @_operation!='removed'))]";
  779. }
  780. $oFieldNode = $this->_priv_GetNodes($sXPath, $oClassNode)->item(0);
  781. if (($oFieldNode == null) && ($oClassNode->getAttribute('parent') != ''))
  782. {
  783. return $this->GetField($oClassNode->getAttribute('parent'), $sAttCode, $bFlattenLayers);
  784. }
  785. return $oFieldNode;
  786. }
  787. /**
  788. * List all classes from the DOM
  789. * @param bool $bFlattenLayers
  790. * @throws Exception
  791. */
  792. public function ListFields(DOMNode $oClassNode, $bFlattenLayers = true)
  793. {
  794. $sXPath = "fields/field";
  795. if ($bFlattenLayers)
  796. {
  797. $sXPath = "fields/field[not(@_operation) or @_operation!='removed']";
  798. }
  799. return $this->_priv_GetNodes($sXPath, $oClassNode);
  800. }
  801. public function AddField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  802. {
  803. $oNewField = $this->oDOMDocument->createElement('field');
  804. $oNewField->setAttribute('name', $sFieldCode);
  805. $this->_priv_AlterField($oNewField, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  806. $oFields = $oClassNode->getElementsByTagName('fields')->item(0);
  807. $oFields->AppendChild($oNewField);
  808. $this->_priv_SetFlag($oNewField, 'added');
  809. }
  810. public function RemoveField(DOMNode $oClassNode, $sFieldCode)
  811. {
  812. $sXPath = "fields/field[@name='$sFieldCode']";
  813. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  814. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  815. {
  816. $oFieldNode = $oFieldNodes->item(0);
  817. $sOpCode = $oFieldNode->getAttribute('_operation');
  818. if ($oFieldNode->getAttribute('_operation') == 'added')
  819. {
  820. $oFieldNode->parentNode->removeChild($oFieldNode);
  821. }
  822. else
  823. {
  824. $this->_priv_SetFlag($oFieldNode, 'removed');
  825. }
  826. }
  827. }
  828. public function AlterField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  829. {
  830. $sXPath = "fields/field[@name='$sFieldCode']";
  831. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  832. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  833. {
  834. $oFieldNode = $oFieldNodes->item(0);
  835. //@@TODO: if the field was 'added' => then let it as 'added'
  836. $sOpCode = $oFieldNode->getAttribute('_operation');
  837. switch($sOpCode)
  838. {
  839. case 'added':
  840. case 'modified':
  841. // added or modified, let it as it is
  842. break;
  843. default:
  844. $this->_priv_SetFlag($oFieldNodes->item(0), 'modified');
  845. }
  846. $this->_priv_AlterField($oFieldNodes->item(0), $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  847. }
  848. }
  849. protected function _priv_AlterField(DOMNode $oFieldNode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  850. {
  851. switch($sFieldType)
  852. {
  853. case 'Blob':
  854. case 'Boolean':
  855. case 'CaseLog':
  856. case 'Deadline':
  857. case 'Duration':
  858. case 'EmailAddress':
  859. case 'EncryptedString':
  860. case 'HTML':
  861. case 'IPAddress':
  862. case 'LongText':
  863. case 'OQL':
  864. case 'OneWayPassword':
  865. case 'Password':
  866. case 'Percentage':
  867. case 'String':
  868. case 'Text':
  869. case 'Text':
  870. case 'TemplateHTML':
  871. case 'TemplateString':
  872. case 'TemplateText':
  873. case 'URL':
  874. case 'Date':
  875. case 'DateTime':
  876. case 'Decimal':
  877. case 'Integer':
  878. break;
  879. case 'ExternalKey':
  880. $this->_priv_AddFieldAttribute($oFieldNode, 'target_class', $aExtraParams);
  881. // Fall through
  882. case 'HierarchicalKey':
  883. $this->_priv_AddFieldAttribute($oFieldNode, 'on_target_delete', $aExtraParams);
  884. $this->_priv_AddFieldAttribute($oFieldNode, 'filter', $aExtraParams);
  885. break;
  886. case 'ExternalField':
  887. $this->_priv_AddFieldAttribute($oFieldNode, 'extkey_attcode', $aExtraParams);
  888. $this->_priv_AddFieldAttribute($oFieldNode, 'target_attcode', $aExtraParams);
  889. break;
  890. case 'Enum':
  891. $this->_priv_SetFieldValues($oFieldNode, $aExtraParams);
  892. break;
  893. case 'LinkedSetIndirect':
  894. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_remote', $aExtraParams);
  895. // Fall through
  896. case 'LinkedSet':
  897. $this->_priv_AddFieldAttribute($oFieldNode, 'linked_class', $aExtraParams);
  898. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_me', $aExtraParams);
  899. $this->_priv_AddFieldAttribute($oFieldNode, 'count_min', $aExtraParams);
  900. $this->_priv_AddFieldAttribute($oFieldNode, 'count_max', $aExtraParams);
  901. break;
  902. default:
  903. throw(new Exception('Unsupported type of field: '.$sFieldType));
  904. }
  905. $this->_priv_SetFieldDependencies($oFieldNode, $aExtraParams);
  906. $oFieldNode->setAttribute('type', $sFieldType);
  907. $oFieldNode->setAttribute('sql', $sSQL);
  908. $oFieldNode->setAttribute('default_value', $defaultValue);
  909. $oFieldNode->setAttribute('is_null_alllowed', $bIsNullAllowed ? 'true' : 'false');
  910. }
  911. protected function _priv_AddFieldAttribute(DOMNode $oFieldNode, $sAttributeCode, $aExtraParams, $bMandatory = false)
  912. {
  913. $value = array_key_exists($sAttributeCode, $aExtraParams) ? $aExtraParams[$sAttributeCode] : '';
  914. if (($value == '') && (!$bMandatory)) return;
  915. $oFieldNode->setAttribute($sAttributeCode, $value);
  916. }
  917. protected function _priv_SetFieldDependencies($oFieldNode, $aExtraParams)
  918. {
  919. $aDeps = array_key_exists('dependencies', $aExtraParams) ? $aExtraParams['dependencies'] : '';
  920. $oDependencies = $oFieldNode->getElementsByTagName('dependencies')->item(0);
  921. // No dependencies before, and no dependencies to add, exit
  922. if (($oDependencies == null) && ($aDeps == '')) return;
  923. // Remove the previous dependencies
  924. $oFieldNode->removeChild($oDependencies);
  925. // If no dependencies, exit
  926. if ($aDeps == '') return;
  927. // Build the new list of dependencies
  928. $oDependencies = $this->oDOMDocument->createElement('dependencies');
  929. foreach($aDeps as $sAttCode)
  930. {
  931. $oDep = $this->oDOMDocument->createElement('attribute');
  932. $oDep->setAttribute('name', $sAttCode);
  933. $oDependencies->addChild($oDep);
  934. }
  935. $oFieldNode->addChild($oDependencies);
  936. }
  937. protected function _priv_SetFieldValues($oFieldNode, $aExtraParams)
  938. {
  939. $aVals = array_key_exists('values', $aExtraParams) ? $aExtraParams['values'] : '';
  940. $oValues = $oFieldNode->getElementsByTagName('values')->item(0);
  941. // No dependencies before, and no dependencies to add, exit
  942. if (($oValues == null) && ($aVals == '')) return;
  943. // Remove the previous dependencies
  944. $oFieldNode->removeChild($oValues);
  945. // If no dependencies, exit
  946. if ($aVals == '') return;
  947. // Build the new list of dependencies
  948. $oValues = $this->oDOMDocument->createElement('values');
  949. foreach($aVals as $sValue)
  950. {
  951. $oVal = $this->oDOMDocument->createElement('value', $sValue);
  952. $oValues->appendChild($oVal);
  953. }
  954. $oFieldNode->appendChild($oValues);
  955. }
  956. public function SetPresentation(DOMNode $oClassNode, $sPresentationCode, $aPresentation)
  957. {
  958. $oPresentation = $oClassNode->getElementsByTagName('presentation')->item(0);
  959. if (!is_object($oPresentation))
  960. {
  961. $oPresentation = $this->oDOMDocument->createElement('presentation');
  962. $oClassNode->appendChild($oPresentation);
  963. }
  964. $oZlist = $oPresentation->getElementsByTagName($sPresentationCode)->item(0);
  965. if (is_object($oZlist))
  966. {
  967. // Remove the previous Zlist
  968. $oPresentation->removeChild($oZlist);
  969. }
  970. // Create the ZList anew
  971. $oZlist = $this->oDOMDocument->createElement($sPresentationCode);
  972. $oPresentation->appendChild($oZlist);
  973. $this->AddZListItem($oZlist, $aPresentation);
  974. $this->_priv_SetFlag($oZlist, 'replaced');
  975. }
  976. protected function AddZListItem($oXMLNode, $value)
  977. {
  978. if (is_array($value))
  979. {
  980. $oXmlItems = $this->oDOMDocument->CreateElement('items');
  981. $oXMLNode->appendChild($oXmlItems);
  982. foreach($value as $key => $item)
  983. {
  984. $oXmlItem = $this->oDOMDocument->CreateElement('item');
  985. $oXmlItems->appendChild($oXmlItem);
  986. if (is_string($key))
  987. {
  988. $oXmlItem->SetAttribute('key', $key);
  989. }
  990. $this->AddZListItem($oXmlItem, $item);
  991. }
  992. }
  993. else
  994. {
  995. $oXmlText = $this->oDOMDocument->CreateTextNode((string) $value);
  996. $oXMLNode->appendChild($oXmlText);
  997. }
  998. }
  999. public function _priv_SetFlag($oNode, $sFlagValue)
  1000. {
  1001. $sPreviousFlag = $oNode->getAttribute('_operation');
  1002. if ($sPreviousFlag == 'added')
  1003. {
  1004. // Do nothing ??
  1005. }
  1006. else
  1007. {
  1008. $oNode->setAttribute('_operation', $sFlagValue);
  1009. }
  1010. if ($oNode->tagName != 'class')
  1011. {
  1012. $this->_priv_SetFlag($oNode->parentNode, 'modified');
  1013. }
  1014. }
  1015. /**
  1016. * List all transitions from a given state
  1017. * @param DOMNode $oStateNode The state
  1018. * @param bool $bFlattenLayers
  1019. * @throws Exception
  1020. */
  1021. public function ListTransitions(DOMNode $oStateNode, $bFlattenLayers = true)
  1022. {
  1023. $sXPath = "transitions/transition";
  1024. if ($bFlattenLayers)
  1025. {
  1026. //$sXPath = "transitions/transition[@_operation!='removed']";
  1027. }
  1028. return $this->_priv_GetNodes($sXPath, $oStateNode);
  1029. }
  1030. /**
  1031. * List all states of a given class
  1032. * @param DOMNode $oClassNode The class
  1033. * @param bool $bFlattenLayers
  1034. * @throws Exception
  1035. */
  1036. public function ListStates(DOMNode $oClassNode, $bFlattenLayers = true)
  1037. {
  1038. $sXPath = "lifecycle/states/state";
  1039. if ($bFlattenLayers)
  1040. {
  1041. //$sXPath = "lifecycle/states/state[@_operation!='removed']";
  1042. }
  1043. return $this->_priv_GetNodes($sXPath, $oClassNode);
  1044. }
  1045. /**
  1046. * List Zlists from the DOM for a given class
  1047. * @param bool $bFlattenLayers
  1048. * @throws Exception
  1049. */
  1050. public function ListZLists(DOMNode $oClassNode, $bFlattenLayers = true)
  1051. {
  1052. // Not yet implemented !!!
  1053. return array();
  1054. }
  1055. /**
  1056. * List all menus from the DOM, for a given module
  1057. * @param string $sModuleName
  1058. * @param bool $bFlattenLayers
  1059. * @throws Exception
  1060. */
  1061. public function ListMenus($sModuleName, $bFlattenLayers = true)
  1062. {
  1063. $sXPath = "//menu[@_created_in='$sModuleName']";
  1064. if ($bFlattenLayers)
  1065. {
  1066. $sXPath = "//menu[@_created_in='$sModuleName' and @_operation!='removed']";
  1067. }
  1068. return $this->_priv_GetNodes($sXPath, $this->oMenus);
  1069. }
  1070. /**
  1071. * Get a menu, given its is id
  1072. * @param string $sModuleName
  1073. * @param bool $bFlattenLayers
  1074. * @throws Exception
  1075. */
  1076. public function GetMenu($sMenuId, $bFlattenLayers = true)
  1077. {
  1078. if (!array_key_exists($sMenuId, self::$aLoadedMenus))
  1079. {
  1080. return null;
  1081. }
  1082. $oMenuNode = self::$aLoadedMenus[$sMenuId];
  1083. if ($bFlattenLayers)
  1084. {
  1085. $sOperation = $oMenuNode->getAttribute('_operation');
  1086. if ($sOperation == 'removed')
  1087. {
  1088. $oMenuNode = null;
  1089. }
  1090. }
  1091. return $oMenuNode;
  1092. }
  1093. public function ApplyChanges()
  1094. {
  1095. $oNodes = $this->ListChanges();
  1096. foreach($oNodes as $oClassNode)
  1097. {
  1098. $sOperation = $oClassNode->GetAttribute('_operation');
  1099. switch($sOperation)
  1100. {
  1101. case 'added':
  1102. case 'modified':
  1103. // marked as added or modified, just reset the flag
  1104. $oClassNode->setAttribute('_operation', '');
  1105. break;
  1106. case 'removed':
  1107. // marked as deleted, let's remove the node from the tree
  1108. $oParent = $oClassNode->parentNode;
  1109. $sClass = $oClassNode->GetAttribute('name');
  1110. echo "Calling removeChild...<br/>";
  1111. $oParent->removeChild($oClassNode);
  1112. unset(self::$aLoadedClasses[$sClass]);
  1113. break;
  1114. }
  1115. }
  1116. }
  1117. public function ListChanges()
  1118. {
  1119. $sXPath = "//*[@_operation!='']";
  1120. return $this->_priv_GetNodes($sXPath);
  1121. }
  1122. /**
  1123. * Get the text/XML version of the delta
  1124. */
  1125. public function GetDelta()
  1126. {
  1127. $oDelta = new DOMDocument('1.0', 'UTF-8');
  1128. $oRootNode = $oDelta->createElement('itop_design');
  1129. $oDelta->appendChild($oRootNode);
  1130. $oClasses = $oDelta->createElement('classes');
  1131. $oRootNode->appendChild($oClasses);
  1132. $this->_priv_ImportModifiedChildren($oDelta, $oClasses, $this->oDOMDocument->firstChild);
  1133. //file_put_contents($sXMLDestPath, $oDelta->saveXML());
  1134. return $oDelta->saveXML();
  1135. }
  1136. protected function _priv_ImportModifiedChildren(DOMDocument $oDoc, DOMNode $oDestNode, DOMNode $oNode)
  1137. {
  1138. static $iDepth = 0;
  1139. $iDepth++;
  1140. foreach($oNode->childNodes as $oChildNode)
  1141. {
  1142. $sNodeName = $oChildNode->nodeName;
  1143. $sNodeValue = $oChildNode->nodeValue;
  1144. if (!$oChildNode instanceof DOMElement)
  1145. {
  1146. $sName = '$$';
  1147. $sOperation = $oChildNode->parentNode->getAttribute('_operation');
  1148. }
  1149. else
  1150. {
  1151. $sName = $oChildNode->getAttribute('name');;
  1152. $sOperation = $oChildNode->getAttribute('_operation');
  1153. }
  1154. echo str_repeat('+', $iDepth)." $sNodeName [$sName], operation: $sOperation\n";
  1155. switch($sOperation)
  1156. {
  1157. case 'removed':
  1158. $oDeletedNode = $oDoc->importNode($oChildNode->cloneNode(false)); // Copies all the node's attributes, but NOT the child nodes
  1159. $oDeletedNode->removeAttribute('_source');
  1160. if ($oDeletedNode->tagName == 'class')
  1161. {
  1162. // classes are always located under the root node
  1163. $oDoc->firstChild->appendChild($oDeletedNode);
  1164. }
  1165. else
  1166. {
  1167. $oDestNode->appendChild($oDeletedNode);
  1168. }
  1169. echo "<p>".str_repeat('+', $iDepth).$oChildNode->getAttribute('name')." was removed...</p>";
  1170. break;
  1171. case 'added':
  1172. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was created...</p>";
  1173. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes as well
  1174. if ($oChildNode instanceof DOMElement)
  1175. {
  1176. $oModifiedNode->removeAttribute('_source');
  1177. if ($oModifiedNode->tagName == 'class')
  1178. {
  1179. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." inserting under 'classes'...</p>";
  1180. // classes are always located under the root node
  1181. $oDoc->firstChild->appendChild($oModifiedNode);
  1182. // Process any subclasses: move them under the root of the document
  1183. $oSubclasses = $oModifiedNode->GetElementsByTagName('class');
  1184. $aSubClasses = array();
  1185. foreach($oSubclasses as $oSubclassNode)
  1186. {
  1187. $aSubClasses[] = $oSubclassNode;
  1188. }
  1189. for($index = count($aSubClasses)-1; $index >= 0; $index--)
  1190. {
  1191. $aSubClasses[$index]->parentNode->removeChild($aSubClasses[$index]);
  1192. $oDoc->firstChild->appendChild($aSubClasses[$index]);
  1193. }
  1194. }
  1195. else
  1196. {
  1197. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." inserting in the hierarchy...</p>";
  1198. $oDestNode->appendChild($oModifiedNode);
  1199. }
  1200. }
  1201. else
  1202. {
  1203. $oDestNode->appendChild($oModifiedNode);
  1204. }
  1205. break;
  1206. case 'replaced':
  1207. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was replaced...</p>";
  1208. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes as well
  1209. if ($oChildNode instanceof DOMElement)
  1210. {
  1211. $oModifiedNode->removeAttribute('_source');
  1212. }
  1213. $oDestNode->appendChild($oModifiedNode);
  1214. break;
  1215. case 'modified':
  1216. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was modified...</p>";
  1217. if ($oChildNode instanceof DOMElement)
  1218. {
  1219. echo str_repeat('+', $iDepth)." Copying (NON recursively) the modified node\n";
  1220. $oModifiedNode = $oDoc->importNode($oChildNode, false); // Copies all the node's attributes, but NOT the child nodes
  1221. $oModifiedNode->removeAttribute('_source');
  1222. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  1223. if ($oModifiedNode->tagName == 'class')
  1224. {
  1225. // classes are always located under the root node
  1226. $oDoc->firstChild->appendChild($oModifiedNode);
  1227. }
  1228. else
  1229. {
  1230. $oDestNode->appendChild($oModifiedNode);
  1231. }
  1232. }
  1233. else
  1234. {
  1235. echo str_repeat('+', $iDepth)." Copying (recursively) the modified node\n";
  1236. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes
  1237. $oDestNode->appendChild($oModifiedNode);
  1238. }
  1239. break;
  1240. default:
  1241. // No change: do nothing
  1242. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was NOT modified...</p>";
  1243. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Importing the node for future recusrsion if needed
  1244. if ($oChildNode->tagName == 'class')
  1245. {
  1246. echo "<p>".str_repeat('+', $iDepth)."Checking if a subclass of ".$oChildNode->getAttribute('name')." was modified...</p>";
  1247. // classes are always located under the root node
  1248. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  1249. }
  1250. }
  1251. }
  1252. $iDepth--;
  1253. }
  1254. /**
  1255. * Searches on disk in the root directory for module description files
  1256. * and returns an array of MFModules
  1257. * @return array Array of MFModules
  1258. */
  1259. public function FindModules($sSubDirectory = '')
  1260. {
  1261. $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->sRootDir, $sSubDirectory);
  1262. $aResult = array();
  1263. foreach($aAvailableModules as $sId => $aModule)
  1264. {
  1265. $aResult[] = new MFModule($sId, $aModule['root_dir'], $aModule['label']);
  1266. }
  1267. return $aResult;
  1268. }
  1269. /**
  1270. * Produce the PHP files corresponding to the data model
  1271. * @param $bTestOnly bool
  1272. * @return array Array of errors, empty if no error
  1273. */
  1274. public function Compile($bTestOnly)
  1275. {
  1276. return array();
  1277. }
  1278. /**
  1279. * Extracts some nodes from the DOM
  1280. * @param string $sXPath A XPath expression
  1281. * @return DOMNodeList
  1282. */
  1283. public function _priv_GetNodes($sXPath, $oContextNode = null)
  1284. {
  1285. $oXPath = new DOMXPath($this->oDOMDocument);
  1286. if (is_null($oContextNode))
  1287. {
  1288. return $oXPath->query($sXPath);
  1289. }
  1290. else
  1291. {
  1292. return $oXPath->query($sXPath, $oContextNode);
  1293. }
  1294. }
  1295. /**
  1296. * Insert a new node in the DOM
  1297. * @param DOMNode $oNode
  1298. * @param DOMNode $oParentNode
  1299. */
  1300. public function _priv_AddNode(DOMNode $oNode, DOMNode $oParentNode)
  1301. {
  1302. }
  1303. /**
  1304. * Remove a node from the DOM
  1305. * @param DOMNode $oNode
  1306. * @param DOMNode $oParentNode
  1307. */
  1308. public function _priv_RemoveNode(DOMNode $oNode)
  1309. {
  1310. }
  1311. /**
  1312. * Add or modify an attribute of a node
  1313. * @param DOMNode $oNode
  1314. * @param string $sAttributeName
  1315. * @param mixed $atttribueValue
  1316. */
  1317. public function _priv_SetNodeAttribute(DOMNode $oNode, $sAttributeName, $atttribueValue)
  1318. {
  1319. }
  1320. /**
  1321. * Helper to browse the DOM -could be factorized in ModelFactory
  1322. * Returns the node directly under the given node, and that is supposed to be always present and unique
  1323. */
  1324. protected function GetUniqueElement($oDOMNode, $sTagName, $bMustExist = true)
  1325. {
  1326. $oNode = null;
  1327. foreach($oDOMNode->childNodes as $oChildNode)
  1328. {
  1329. if ($oChildNode->nodeName == $sTagName)
  1330. {
  1331. $oNode = $oChildNode;
  1332. break;
  1333. }
  1334. }
  1335. if ($bMustExist && is_null($oNode))
  1336. {
  1337. throw new DOMFormatException('Missing unique tag: '.$sTagName);
  1338. }
  1339. return $oNode;
  1340. }
  1341. /**
  1342. * Helper to browse the DOM -could be factorized in ModelFactory
  1343. * Returns the node directly under the given node, or null is missing
  1344. */
  1345. protected function GetOptionalElement($oDOMNode, $sTagName)
  1346. {
  1347. return $this->GetUniqueElement($oDOMNode, $sTagName, false);
  1348. }
  1349. /**
  1350. * Helper to browse the DOM -could be factorized in ModelFactory
  1351. * Returns the TEXT of the given node (possibly from several subnodes)
  1352. */
  1353. protected function GetNodeText($oNode)
  1354. {
  1355. $sText = '';
  1356. foreach($oNode->childNodes as $oChildNode)
  1357. {
  1358. if ($oChildNode instanceof DOMCharacterData) // Base class of DOMText and DOMCdataSection
  1359. {
  1360. $sText .= $oChildNode->wholeText;
  1361. }
  1362. }
  1363. return $sText;
  1364. }
  1365. /**
  1366. * Helper to browse the DOM -could be factorized in ModelFactory
  1367. * Assumes the given node to be either a text or
  1368. * <items>
  1369. * <item [key]="..."]>value<item>
  1370. * <item [key]="..."]>value<item>
  1371. * </items>
  1372. * where value can be the either a text or an array of items... recursively
  1373. * Returns a PHP array
  1374. */
  1375. public function GetNodeAsArrayOfItems($oNode)
  1376. {
  1377. $oItems = $this->GetOptionalElement($oNode, 'items');
  1378. if ($oItems)
  1379. {
  1380. $res = array();
  1381. foreach($oItems->childNodes as $oItem)
  1382. {
  1383. // When an attribute is missing
  1384. if ($oItem->hasAttribute('key'))
  1385. {
  1386. $key = $oItem->getAttribute('key');
  1387. $res[$key] = $this->GetNodeAsArrayOfItems($oItem);
  1388. }
  1389. else
  1390. {
  1391. $res[] = $this->GetNodeAsArrayOfItems($oItem);
  1392. }
  1393. }
  1394. }
  1395. else
  1396. {
  1397. $res = $this->GetNodeText($oNode);
  1398. }
  1399. return $res;
  1400. }
  1401. }