modelfactory.class.inc.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410
  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. static protected $aLoadedClasses;
  237. static protected $aWellKnownParents = array('DBObject', 'CMDBObject','cmdbAbstractObject');
  238. static protected $aLoadedModules;
  239. public function __construct($sRootDir)
  240. {
  241. $this->sRootDir = $sRootDir;
  242. $this->oDOMDocument = new DOMDocument('1.0', 'UTF-8');
  243. $this->oRoot = $this->oDOMDocument->CreateElement('itop_design');
  244. $this->oDOMDocument->AppendChild($this->oRoot);
  245. $this->oClasses = $this->oDOMDocument->CreateElement('classes');
  246. $this->oRoot->AppendChild($this->oClasses);
  247. self::$aLoadedClasses = array();
  248. self::$aLoadedModules = array();
  249. }
  250. public function Dump($oNode = null)
  251. {
  252. if (is_null($oNode))
  253. {
  254. $oNode = $this->oClasses;
  255. }
  256. echo htmlentities($this->oDOMDocument->saveXML($oNode));
  257. }
  258. /**
  259. * Loads the definitions corresponding to the given Module
  260. * @param MFModule $oModule
  261. */
  262. public function LoadModule(MFModule $oModule)
  263. {
  264. $aDataModels = $oModule->GetDataModelFiles();
  265. $sModuleName = $oModule->GetName();
  266. $aClasses = array();
  267. self::$aLoadedModules[] = $oModule;
  268. foreach($aDataModels as $sXmlFile)
  269. {
  270. $oDocument = new DOMDocument('1.0', 'UTF-8');
  271. $oDocument->load($sXmlFile, LIBXML_NOBLANKS);
  272. $oXPath = new DOMXPath($oDocument);
  273. $oNodeList = $oXPath->query('//*');
  274. foreach($oNodeList as $oNode)
  275. {
  276. $oNode->SetAttribute('_source', $sXmlFile);
  277. }
  278. $oXPath = new DOMXPath($oDocument);
  279. $oNodeList = $oXPath->query('/itop_design/classes/class');
  280. foreach($oNodeList as $oNode)
  281. {
  282. if ($oNode->hasAttribute('parent'))
  283. {
  284. $sParentClass = $oNode->GetAttribute('parent');
  285. }
  286. else
  287. {
  288. $sParentClass = '';
  289. }
  290. $sClassName = $oNode->GetAttribute('name');
  291. $aClasses[$sClassName] = array('name' => $sClassName, 'parent' => $sParentClass, 'node' => $oNode);
  292. }
  293. }
  294. $index = 1;
  295. do
  296. {
  297. $bNothingLoaded = true;
  298. foreach($aClasses as $sClassName => $aClassData)
  299. {
  300. $sOperation = $aClassData['node']->getAttribute('_operation');
  301. switch($sOperation)
  302. {
  303. case 'added':
  304. case '':
  305. if (in_array($aClassData['parent'], self::$aWellKnownParents))
  306. {
  307. $this->AddClass($aClassData['node'], $sModuleName);
  308. unset($aClasses[$sClassName]);
  309. $bNothingLoaded = false;
  310. }
  311. else if ($this->ClassNameExists($aClassData['parent']))
  312. {
  313. $this->AddClass($aClassData['node'], $sModuleName);
  314. unset($aClasses[$sClassName]);
  315. $bNothingLoaded = false;
  316. }
  317. break;
  318. case 'removed':
  319. unset($aClasses[$sClassName]);
  320. $this->RemoveClass($sClassName);
  321. break;
  322. case 'modified':
  323. unset($aClasses[$sClassName]);
  324. $this->AlterClass($sClassName, $aClassData['node']);
  325. break;
  326. }
  327. }
  328. $index++;
  329. }
  330. while((count($aClasses)>0) && !$bNothingLoaded);
  331. // The remaining classes have no known parent, let's add them at the root
  332. foreach($aClasses as $sClassName => $aClassData)
  333. {
  334. $sOperation = $aClassData['node']->getAttribute('_operation');
  335. switch($sOperation)
  336. {
  337. case 'added':
  338. case '':
  339. // Add the class as a new root class
  340. $this->AddClass($aClassData['node'], $sModuleName);
  341. $oNewNode = $this->oDOMDocument->ImportNode($aClassData['node']);
  342. break;
  343. case 'removed':
  344. $this->RemoveClass($sClassName);
  345. break;
  346. case 'modified':
  347. //@@TODO Handle the modification of a class here
  348. $this->AlterClass($sClassName, $aClassData['node']);
  349. break;
  350. }
  351. }
  352. }
  353. function GetLoadedModules($bExcludeWorkspace = true)
  354. {
  355. if ($bExcludeWorkspace)
  356. {
  357. $aModules = array();
  358. foreach(self::$aLoadedModules as $oModule)
  359. {
  360. if (!$oModule instanceof MFWorkspace)
  361. {
  362. $aModules[] = $oModule;
  363. }
  364. }
  365. }
  366. else
  367. {
  368. $aModules = self::$aLoadedModules;
  369. }
  370. return $aModules;
  371. }
  372. function GetModule($sModuleName)
  373. {
  374. foreach(self::$aLoadedModules as $oModule)
  375. {
  376. if ($oModule->GetName() == $sModuleName) return $oModule;
  377. }
  378. return null;
  379. }
  380. /**
  381. * Check if the class specified by the given node already exists in the loaded DOM
  382. * @param DOMNode $oClassNode The node corresponding to the class to load
  383. * @throws Exception
  384. * @return bool True if the class exists, false otherwise
  385. */
  386. protected function ClassExists(DOMNode $oClassNode)
  387. {
  388. if ($oClassNode->hasAttribute('name'))
  389. {
  390. $sClassName = $oClassNode->GetAttribute('name');
  391. }
  392. else
  393. {
  394. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  395. }
  396. return (array_key_exists($sClassName, self::$aLoadedClasses));
  397. }
  398. /**
  399. * Check if the class specified by the given name already exists in the loaded DOM
  400. * @param string $sClassName The node corresponding to the class to load
  401. * @throws Exception
  402. * @return bool True if the class exists, false otherwise
  403. */
  404. protected function ClassNameExists($sClassName)
  405. {
  406. return (array_key_exists($sClassName, self::$aLoadedClasses));
  407. }
  408. /**
  409. * Add the given class to the DOM
  410. * @param DOMNode $oClassNode
  411. * @param string $sModuleName The name of the module in which this class is declared
  412. * @throws Exception
  413. */
  414. public function AddClass(DOMNode $oClassNode, $sModuleName)
  415. {
  416. if ($oClassNode->hasAttribute('name'))
  417. {
  418. $sClassName = $oClassNode->GetAttribute('name');
  419. }
  420. else
  421. {
  422. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  423. }
  424. if ($this->ClassExists($oClassNode))
  425. {
  426. throw new Exception("ModelFactory::AddClass: Cannot add the already existing class $sClassName");
  427. }
  428. $sParentClass = '';
  429. if ($oClassNode->hasAttribute('parent'))
  430. {
  431. $sParentClass = $oClassNode->GetAttribute('parent');
  432. }
  433. //echo "Adding class: $sClassName, parent: $sParentClass<br/>";
  434. if (!in_array($sParentClass, self::$aWellKnownParents) && $this->ClassNameExists($sParentClass))
  435. {
  436. // The class is a subclass of a class already loaded, add it under
  437. self::$aLoadedClasses[$sClassName] = $this->oDOMDocument->ImportNode($oClassNode, true /* bDeep */);
  438. self::$aLoadedClasses[$sClassName]->SetAttribute('_operation', 'added');
  439. if ($sModuleName != '')
  440. {
  441. self::$aLoadedClasses[$sClassName]->SetAttribute('_created_in', $sModuleName);
  442. }
  443. self::$aLoadedClasses[$sParentClass]->AppendChild(self::$aLoadedClasses[$sClassName]);
  444. $bNothingLoaded = false;
  445. }
  446. else if (in_array($sParentClass, self::$aWellKnownParents))
  447. {
  448. // Add the class as a new root class
  449. self::$aLoadedClasses[$sClassName] = $this->oDOMDocument->ImportNode($oClassNode, true /* bDeep */);
  450. self::$aLoadedClasses[$sClassName]->SetAttribute('_operation', 'added');
  451. if ($sModuleName != '')
  452. {
  453. self::$aLoadedClasses[$sClassName]->SetAttribute('_created_in', $sModuleName);
  454. }
  455. $this->oClasses->AppendChild(self::$aLoadedClasses[$sClassName]);
  456. }
  457. else
  458. {
  459. throw new Exception("ModelFactory::AddClass: Cannot add the class $sClassName, unknown parent class: $sParentClass");
  460. }
  461. }
  462. /**
  463. * Remove a class from the DOM
  464. * @param string $sClass
  465. * @throws Exception
  466. */
  467. public function RemoveClass($sClass)
  468. {
  469. if (!$this->ClassNameExists($sClass))
  470. {
  471. throw new Exception("ModelFactory::RemoveClass: Cannot remove the non existing class $sClass");
  472. }
  473. $oClassNode = self::$aLoadedClasses[$sClass];
  474. if ($oClassNode->getAttribute('_operation') == 'added')
  475. {
  476. $oClassNode->parentNode->RemoveChild($oClassNode);
  477. unset(self::$aLoadedClasses[$sClass]);
  478. }
  479. else
  480. {
  481. self::$aLoadedClasses[$sClass]->SetAttribute('_operation', 'removed');
  482. //TODO: also mark as removed the child classes
  483. }
  484. }
  485. public function AlterClass($sClassName, DOMNode $oClassNode)
  486. {
  487. $sOriginalName = $sClassName;
  488. if ($this->ClassNameExists($sClassName))
  489. {
  490. $oDestNode = self::$aLoadedClasses[$sClassName];
  491. }
  492. else
  493. {
  494. $sOriginalName = $oClassNode->getAttribute('_original_name');
  495. if ($this->ClassNameExists($sOriginalName))
  496. {
  497. // Class was renamed !
  498. $oDestNode = self::$aLoadedClasses[$sOriginalName];
  499. }
  500. else
  501. {
  502. throw new Exception("ModelFactory::AddClass: Cannot alter the non-existing class $sClassName / $sOriginalName");
  503. }
  504. }
  505. $this->_priv_AlterNode($oDestNode, $oClassNode);
  506. $sClassName = $oDestNode->getAttribute('name');
  507. if ($sOriginalName != $sClassName)
  508. {
  509. unset(self::$aLoadedClasses[$sOriginalName]);
  510. self::$aLoadedClasses[$sClassName] = $oDestNode;
  511. }
  512. $this->_priv_SetFlag($oDestNode, 'modified');
  513. }
  514. protected function _priv_AlterNode(DOMNode $oNode, DOMNode $oDeltaNode)
  515. {
  516. foreach ($oDeltaNode->attributes as $sName => $oAttrNode)
  517. {
  518. $sCurrentValue = $oNode->getAttribute($sName);
  519. $sNewValue = $oAttrNode->value;
  520. $oNode->setAttribute($sName, $oAttrNode->value);
  521. }
  522. $aSrcChildNodes = $oNode->childNodes;
  523. foreach($oDeltaNode->childNodes as $index => $oChildNode)
  524. {
  525. if (!$oChildNode instanceof DOMElement)
  526. {
  527. // Text or CData nodes are treated by position
  528. $sOperation = $oChildNode->parentNode->getAttribute('_operation');
  529. switch($sOperation)
  530. {
  531. case 'removed':
  532. // ???
  533. break;
  534. case 'modified':
  535. case 'replaced':
  536. case 'added':
  537. $oNewNode = $this->oDOMDocument->importNode($oChildNode);
  538. $oSrcChildNode = $aSrcChildNodes->item($index);
  539. if ($oSrcChildNode)
  540. {
  541. $oNode->replaceChild($oNewNode, $oSrcChildNode);
  542. }
  543. else
  544. {
  545. $oNode->appendChild($oNewNode);
  546. }
  547. break;
  548. case '':
  549. // Do nothing
  550. }
  551. }
  552. else
  553. {
  554. $sOperation = $oChildNode->getAttribute('_operation');
  555. $sPath = $oChildNode->tagName;
  556. $sName = $oChildNode->getAttribute('name');
  557. if ($sName != '')
  558. {
  559. $sPath .= "[@name='$sName']";
  560. }
  561. switch($sOperation)
  562. {
  563. case 'removed':
  564. $oToRemove = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  565. if ($oToRemove != null)
  566. {
  567. $this->_priv_SetFlag($oToRemove, 'removed');
  568. }
  569. break;
  570. case 'modified':
  571. $oToModify = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  572. if ($oToModify != null)
  573. {
  574. $this->_priv_AlterNode($oToModify, $oChildNode);
  575. }
  576. else
  577. {
  578. throw new Exception("Cannot modify the non-existing node '$sPath' in '".$oNode->getNodePath()."'");
  579. }
  580. break;
  581. case 'replaced':
  582. $oNewNode = $this->oDOMDocument->importNode($oChildNode, true); // Import the node and its child nodes
  583. $oToModify = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  584. $oNode->replaceChild($oNewNode, $oToModify);
  585. break;
  586. case 'added':
  587. $oNewNode = $this->oDOMDocument->importNode($oChildNode);
  588. $oNode->appendChild($oNewNode);
  589. $this->_priv_SetFlag($oNewNode, 'added');
  590. break;
  591. case '':
  592. // Do nothing
  593. }
  594. }
  595. }
  596. }
  597. public function GetClassXMLTemplate($sName, $sIcon)
  598. {
  599. return
  600. <<<EOF
  601. <?xml version="1.0" encoding="utf-8"?>
  602. <class name="$sName" parent="" db_table="" category="" abstract="" key_type="autoincrement" db_key_field="id" db_final_class_field="finalclass">
  603. <properties>
  604. <comment/>
  605. <naming format=""><attributes/></naming>
  606. <reconciliation><attributes/></reconciliation>
  607. <display_template/>
  608. <icon>$sIcon</icon>
  609. </properties>
  610. <fields/>
  611. <methods/>
  612. <presentation>
  613. <details><items/></details>
  614. <search><items/></search>
  615. <list><items/></list>
  616. </presentation>
  617. </class>
  618. EOF
  619. ;
  620. }
  621. /**
  622. * List all classes from the DOM, for a given module
  623. * @param string $sModuleNale
  624. * @param bool $bFlattenLayers
  625. * @throws Exception
  626. */
  627. public function ListClasses($sModuleName, $bFlattenLayers = true)
  628. {
  629. $sXPath = "//class[@_created_in='$sModuleName']";
  630. if ($bFlattenLayers)
  631. {
  632. $sXPath = "//class[@_created_in='$sModuleName' and @_operation!='removed']";
  633. }
  634. return $this->_priv_GetNodes($sXPath);
  635. }
  636. /**
  637. * List all classes from the DOM, for a given module
  638. * @param string $sModuleNale
  639. * @param bool $bFlattenLayers
  640. * @throws Exception
  641. */
  642. public function ListAllClasses($bFlattenLayers = true)
  643. {
  644. $sXPath = "//class";
  645. if ($bFlattenLayers)
  646. {
  647. $sXPath = "//class[@_operation!='removed']";
  648. }
  649. return $this->_priv_GetNodes($sXPath);
  650. }
  651. public function GetClass($sClassName, $bFlattenLayers = true)
  652. {
  653. if (!$this->ClassNameExists($sClassName))
  654. {
  655. return null;
  656. }
  657. $oClassNode = self::$aLoadedClasses[$sClassName];
  658. if ($bFlattenLayers)
  659. {
  660. $sOperation = $oClassNode->getAttribute('_operation');
  661. if ($sOperation == 'removed')
  662. {
  663. $oClassNode = null;
  664. }
  665. }
  666. return $oClassNode;
  667. }
  668. public function GetChildClasses($oClassNode, $bFlattenLayers = true)
  669. {
  670. $sXPath = "class";
  671. if ($bFlattenLayers)
  672. {
  673. $sXPath = "class[(@_operation!='removed')]";
  674. }
  675. return $this->_priv_GetNodes($sXPath, $oClassNode);
  676. }
  677. public function GetField($sClassName, $sAttCode, $bFlattenLayers = true)
  678. {
  679. if (!$this->ClassNameExists($sClassName))
  680. {
  681. return null;
  682. }
  683. $oClassNode = self::$aLoadedClasses[$sClassName];
  684. if ($bFlattenLayers)
  685. {
  686. $sOperation = $oClassNode->getAttribute('_operation');
  687. if ($sOperation == 'removed')
  688. {
  689. $oClassNode = null;
  690. }
  691. }
  692. $sXPath = "fields/field[@name='$sAttCode']";
  693. if ($bFlattenLayers)
  694. {
  695. $sXPath = "fields/field[(@name='$sAttCode' and (not(@_operation) or @_operation!='removed'))]";
  696. }
  697. $oFieldNode = $this->_priv_GetNodes($sXPath, $oClassNode)->item(0);
  698. if (($oFieldNode == null) && ($oClassNode->getAttribute('parent') != ''))
  699. {
  700. return $this->GetField($oClassNode->getAttribute('parent'), $sAttCode, $bFlattenLayers);
  701. }
  702. return $oFieldNode;
  703. }
  704. /**
  705. * List all classes from the DOM
  706. * @param bool $bFlattenLayers
  707. * @throws Exception
  708. */
  709. public function ListFields(DOMNode $oClassNode, $bFlattenLayers = true)
  710. {
  711. $sXPath = "fields/field";
  712. if ($bFlattenLayers)
  713. {
  714. $sXPath = "fields/field[not(@_operation) or @_operation!='removed']";
  715. }
  716. return $this->_priv_GetNodes($sXPath, $oClassNode);
  717. }
  718. public function AddField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  719. {
  720. $oNewField = $this->oDOMDocument->createElement('field');
  721. $oNewField->setAttribute('name', $sFieldCode);
  722. $this->_priv_AlterField($oNewField, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  723. $oFields = $oClassNode->getElementsByTagName('fields')->item(0);
  724. $oFields->AppendChild($oNewField);
  725. $this->_priv_SetFlag($oNewField, 'added');
  726. }
  727. public function RemoveField(DOMNode $oClassNode, $sFieldCode)
  728. {
  729. $sXPath = "fields/field[@name='$sFieldCode']";
  730. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  731. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  732. {
  733. $oFieldNode = $oFieldNodes->item(0);
  734. $sOpCode = $oFieldNode->getAttribute('_operation');
  735. if ($oFieldNode->getAttribute('_operation') == 'added')
  736. {
  737. $oFieldNode->parentNode->removeChild($oFieldNode);
  738. }
  739. else
  740. {
  741. $this->_priv_SetFlag($oFieldNode, 'removed');
  742. }
  743. }
  744. }
  745. public function AlterField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  746. {
  747. $sXPath = "fields/field[@name='$sFieldCode']";
  748. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  749. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  750. {
  751. $oFieldNode = $oFieldNodes->item(0);
  752. //@@TODO: if the field was 'added' => then let it as 'added'
  753. $sOpCode = $oFieldNode->getAttribute('_operation');
  754. switch($sOpCode)
  755. {
  756. case 'added':
  757. case 'modified':
  758. // added or modified, let it as it is
  759. break;
  760. default:
  761. $this->_priv_SetFlag($oFieldNodes->item(0), 'modified');
  762. }
  763. $this->_priv_AlterField($oFieldNodes->item(0), $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  764. }
  765. }
  766. protected function _priv_AlterField(DOMNode $oFieldNode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  767. {
  768. switch($sFieldType)
  769. {
  770. case 'Blob':
  771. case 'Boolean':
  772. case 'CaseLog':
  773. case 'Deadline':
  774. case 'Duration':
  775. case 'EmailAddress':
  776. case 'EncryptedString':
  777. case 'HTML':
  778. case 'IPAddress':
  779. case 'LongText':
  780. case 'OQL':
  781. case 'OneWayPassword':
  782. case 'Password':
  783. case 'Percentage':
  784. case 'String':
  785. case 'Text':
  786. case 'Text':
  787. case 'TemplateHTML':
  788. case 'TemplateString':
  789. case 'TemplateText':
  790. case 'URL':
  791. case 'Date':
  792. case 'DateTime':
  793. case 'Decimal':
  794. case 'Integer':
  795. break;
  796. case 'ExternalKey':
  797. $this->_priv_AddFieldAttribute($oFieldNode, 'target_class', $aExtraParams);
  798. // Fall through
  799. case 'HierarchicalKey':
  800. $this->_priv_AddFieldAttribute($oFieldNode, 'on_target_delete', $aExtraParams);
  801. $this->_priv_AddFieldAttribute($oFieldNode, 'filter', $aExtraParams);
  802. break;
  803. case 'ExternalField':
  804. $this->_priv_AddFieldAttribute($oFieldNode, 'extkey_attcode', $aExtraParams);
  805. $this->_priv_AddFieldAttribute($oFieldNode, 'target_attcode', $aExtraParams);
  806. break;
  807. case 'Enum':
  808. $this->_priv_SetFieldValues($oFieldNode, $aExtraParams);
  809. break;
  810. case 'LinkedSetIndirect':
  811. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_remote', $aExtraParams);
  812. // Fall through
  813. case 'LinkedSet':
  814. $this->_priv_AddFieldAttribute($oFieldNode, 'linked_class', $aExtraParams);
  815. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_me', $aExtraParams);
  816. $this->_priv_AddFieldAttribute($oFieldNode, 'count_min', $aExtraParams);
  817. $this->_priv_AddFieldAttribute($oFieldNode, 'count_max', $aExtraParams);
  818. break;
  819. default:
  820. throw(new Exception('Unsupported type of field: '.$sFieldType));
  821. }
  822. $this->_priv_SetFieldDependencies($oFieldNode, $aExtraParams);
  823. $oFieldNode->setAttribute('type', $sFieldType);
  824. $oFieldNode->setAttribute('sql', $sSQL);
  825. $oFieldNode->setAttribute('default_value', $defaultValue);
  826. $oFieldNode->setAttribute('is_null_alllowed', $bIsNullAllowed ? 'true' : 'false');
  827. }
  828. protected function _priv_AddFieldAttribute(DOMNode $oFieldNode, $sAttributeCode, $aExtraParams, $bMandatory = false)
  829. {
  830. $value = array_key_exists($sAttributeCode, $aExtraParams) ? $aExtraParams[$sAttributeCode] : '';
  831. if (($value == '') && (!$bMandatory)) return;
  832. $oFieldNode->setAttribute($sAttributeCode, $value);
  833. }
  834. protected function _priv_SetFieldDependencies($oFieldNode, $aExtraParams)
  835. {
  836. $aDeps = array_key_exists('dependencies', $aExtraParams) ? $aExtraParams['dependencies'] : '';
  837. $oDependencies = $oFieldNode->getElementsByTagName('dependencies')->item(0);
  838. // No dependencies before, and no dependencies to add, exit
  839. if (($oDependencies == null) && ($aDeps == '')) return;
  840. // Remove the previous dependencies
  841. $oFieldNode->removeChild($oDependencies);
  842. // If no dependencies, exit
  843. if ($aDeps == '') return;
  844. // Build the new list of dependencies
  845. $oDependencies = $this->oDOMDocument->createElement('dependencies');
  846. foreach($aDeps as $sAttCode)
  847. {
  848. $oDep = $this->oDOMDocument->createElement('attribute');
  849. $oDep->setAttribute('name', $sAttCode);
  850. $oDependencies->addChild($oDep);
  851. }
  852. $oFieldNode->addChild($oDependencies);
  853. }
  854. protected function _priv_SetFieldValues($oFieldNode, $aExtraParams)
  855. {
  856. $aVals = array_key_exists('values', $aExtraParams) ? $aExtraParams['values'] : '';
  857. $oValues = $oFieldNode->getElementsByTagName('values')->item(0);
  858. // No dependencies before, and no dependencies to add, exit
  859. if (($oValues == null) && ($aVals == '')) return;
  860. // Remove the previous dependencies
  861. $oFieldNode->removeChild($oValues);
  862. // If no dependencies, exit
  863. if ($aVals == '') return;
  864. // Build the new list of dependencies
  865. $oValues = $this->oDOMDocument->createElement('values');
  866. foreach($aVals as $sValue)
  867. {
  868. $oVal = $this->oDOMDocument->createElement('value', $sValue);
  869. $oValues->appendChild($oVal);
  870. }
  871. $oFieldNode->appendChild($oValues);
  872. }
  873. public function SetPresentation(DOMNode $oClassNode, $sPresentationCode, $aPresentation)
  874. {
  875. $oPresentation = $oClassNode->getElementsByTagName('presentation')->item(0);
  876. if (!is_object($oPresentation))
  877. {
  878. $oPresentation = $this->oDOMDocument->createElement('presentation');
  879. $oClassNode->appendChild($oPresentation);
  880. }
  881. $oZlist = $oPresentation->getElementsByTagName($sPresentationCode)->item(0);
  882. if (is_object($oZlist))
  883. {
  884. // Remove the previous Zlist
  885. $oPresentation->removeChild($oZlist);
  886. }
  887. // Create the ZList anew
  888. $oZlist = $this->oDOMDocument->createElement($sPresentationCode);
  889. $oPresentation->appendChild($oZlist);
  890. $this->AddZListItem($oZlist, $aPresentation);
  891. $this->_priv_SetFlag($oZlist, 'replaced');
  892. }
  893. protected function AddZListItem($oXMLNode, $value)
  894. {
  895. if (is_array($value))
  896. {
  897. $oXmlItems = $this->oDOMDocument->CreateElement('items');
  898. $oXMLNode->appendChild($oXmlItems);
  899. foreach($value as $key => $item)
  900. {
  901. $oXmlItem = $this->oDOMDocument->CreateElement('item');
  902. $oXmlItems->appendChild($oXmlItem);
  903. if (is_string($key))
  904. {
  905. $oXmlItem->SetAttribute('key', $key);
  906. }
  907. $this->AddZListItem($oXmlItem, $item);
  908. }
  909. }
  910. else
  911. {
  912. $oXmlText = $this->oDOMDocument->CreateTextNode((string) $value);
  913. $oXMLNode->appendChild($oXmlText);
  914. }
  915. }
  916. public function _priv_SetFlag($oNode, $sFlagValue)
  917. {
  918. $sPreviousFlag = $oNode->getAttribute('_operation');
  919. if ($sPreviousFlag == 'added')
  920. {
  921. // Do nothing ??
  922. }
  923. else
  924. {
  925. $oNode->setAttribute('_operation', $sFlagValue);
  926. }
  927. if ($oNode->tagName != 'class')
  928. {
  929. $this->_priv_SetFlag($oNode->parentNode, 'modified');
  930. }
  931. }
  932. /**
  933. * List all transitions from a given state
  934. * @param DOMNode $oStateNode The state
  935. * @param bool $bFlattenLayers
  936. * @throws Exception
  937. */
  938. public function ListTransitions(DOMNode $oStateNode, $bFlattenLayers = true)
  939. {
  940. $sXPath = "transitions/transition";
  941. if ($bFlattenLayers)
  942. {
  943. //$sXPath = "transitions/transition[@_operation!='removed']";
  944. }
  945. return $this->_priv_GetNodes($sXPath, $oStateNode);
  946. }
  947. /**
  948. * List all states of a given class
  949. * @param DOMNode $oClassNode The class
  950. * @param bool $bFlattenLayers
  951. * @throws Exception
  952. */
  953. public function ListStates(DOMNode $oClassNode, $bFlattenLayers = true)
  954. {
  955. $sXPath = "lifecycle/states/state";
  956. if ($bFlattenLayers)
  957. {
  958. //$sXPath = "lifecycle/states/state[@_operation!='removed']";
  959. }
  960. return $this->_priv_GetNodes($sXPath, $oClassNode);
  961. }
  962. /**
  963. * List Zlists from the DOM for a given class
  964. * @param bool $bFlattenLayers
  965. * @throws Exception
  966. */
  967. public function ListZLists(DOMNode $oClassNode, $bFlattenLayers = true)
  968. {
  969. // Not yet implemented !!!
  970. return array();
  971. }
  972. public function ApplyChanges()
  973. {
  974. $oNodes = $this->ListChanges();
  975. foreach($oNodes as $oClassNode)
  976. {
  977. $sOperation = $oClassNode->GetAttribute('_operation');
  978. switch($sOperation)
  979. {
  980. case 'added':
  981. case 'modified':
  982. // marked as added or modified, just reset the flag
  983. $oClassNode->setAttribute('_operation', '');
  984. break;
  985. case 'removed':
  986. // marked as deleted, let's remove the node from the tree
  987. $oParent = $oClassNode->parentNode;
  988. $sClass = $oClassNode->GetAttribute('name');
  989. echo "Calling removeChild...<br/>";
  990. $oParent->removeChild($oClassNode);
  991. unset(self::$aLoadedClasses[$sClass]);
  992. break;
  993. }
  994. }
  995. }
  996. public function ListChanges()
  997. {
  998. $sXPath = "//*[@_operation!='']";
  999. return $this->_priv_GetNodes($sXPath);
  1000. }
  1001. /**
  1002. * Get the text/XML version of the delta
  1003. */
  1004. public function GetDelta()
  1005. {
  1006. $oDelta = new DOMDocument('1.0', 'UTF-8');
  1007. $oRootNode = $oDelta->createElement('itop_design');
  1008. $oDelta->appendChild($oRootNode);
  1009. $oClasses = $oDelta->createElement('classes');
  1010. $oRootNode->appendChild($oClasses);
  1011. $this->_priv_ImportModifiedChildren($oDelta, $oClasses, $this->oDOMDocument->firstChild);
  1012. //file_put_contents($sXMLDestPath, $oDelta->saveXML());
  1013. return $oDelta->saveXML();
  1014. }
  1015. protected function _priv_ImportModifiedChildren(DOMDocument $oDoc, DOMNode $oDestNode, DOMNode $oNode)
  1016. {
  1017. static $iDepth = 0;
  1018. $iDepth++;
  1019. foreach($oNode->childNodes as $oChildNode)
  1020. {
  1021. $sNodeName = $oChildNode->nodeName;
  1022. $sNodeValue = $oChildNode->nodeValue;
  1023. if (!$oChildNode instanceof DOMElement)
  1024. {
  1025. $sName = '$$';
  1026. $sOperation = $oChildNode->parentNode->getAttribute('_operation');
  1027. }
  1028. else
  1029. {
  1030. $sName = $oChildNode->getAttribute('name');;
  1031. $sOperation = $oChildNode->getAttribute('_operation');
  1032. }
  1033. echo str_repeat('+', $iDepth)." $sNodeName [$sName], operation: $sOperation\n";
  1034. switch($sOperation)
  1035. {
  1036. case 'removed':
  1037. $oDeletedNode = $oDoc->importNode($oChildNode->cloneNode(false)); // Copies all the node's attributes, but NOT the child nodes
  1038. $oDeletedNode->removeAttribute('_source');
  1039. if ($oDeletedNode->tagName == 'class')
  1040. {
  1041. // classes are always located under the root node
  1042. $oDoc->firstChild->appendChild($oDeletedNode);
  1043. }
  1044. else
  1045. {
  1046. $oDestNode->appendChild($oDeletedNode);
  1047. }
  1048. echo "<p>".str_repeat('+', $iDepth).$oChildNode->getAttribute('name')." was removed...</p>";
  1049. break;
  1050. case 'added':
  1051. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was created...</p>";
  1052. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes as well
  1053. if ($oChildNode instanceof DOMElement)
  1054. {
  1055. $oModifiedNode->removeAttribute('_source');
  1056. if ($oModifiedNode->tagName == 'class')
  1057. {
  1058. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." inserting under 'classes'...</p>";
  1059. // classes are always located under the root node
  1060. $oDoc->firstChild->appendChild($oModifiedNode);
  1061. // Process any subclasses: move them under the root of the document
  1062. $oSubclasses = $oModifiedNode->GetElementsByTagName('class');
  1063. $aSubClasses = array();
  1064. foreach($oSubclasses as $oSubclassNode)
  1065. {
  1066. $aSubClasses[] = $oSubclassNode;
  1067. }
  1068. for($index = count($aSubClasses)-1; $index >= 0; $index--)
  1069. {
  1070. $aSubClasses[$index]->parentNode->removeChild($aSubClasses[$index]);
  1071. $oDoc->firstChild->appendChild($aSubClasses[$index]);
  1072. }
  1073. }
  1074. else
  1075. {
  1076. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." inserting in the hierarchy...</p>";
  1077. $oDestNode->appendChild($oModifiedNode);
  1078. }
  1079. }
  1080. else
  1081. {
  1082. $oDestNode->appendChild($oModifiedNode);
  1083. }
  1084. break;
  1085. case 'replaced':
  1086. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was replaced...</p>";
  1087. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes as well
  1088. if ($oChildNode instanceof DOMElement)
  1089. {
  1090. $oModifiedNode->removeAttribute('_source');
  1091. }
  1092. $oDestNode->appendChild($oModifiedNode);
  1093. break;
  1094. case 'modified':
  1095. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was modified...</p>";
  1096. if ($oChildNode instanceof DOMElement)
  1097. {
  1098. echo str_repeat('+', $iDepth)." Copying (NON recursively) the modified node\n";
  1099. $oModifiedNode = $oDoc->importNode($oChildNode, false); // Copies all the node's attributes, but NOT the child nodes
  1100. $oModifiedNode->removeAttribute('_source');
  1101. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  1102. if ($oModifiedNode->tagName == 'class')
  1103. {
  1104. // classes are always located under the root node
  1105. $oDoc->firstChild->appendChild($oModifiedNode);
  1106. }
  1107. else
  1108. {
  1109. $oDestNode->appendChild($oModifiedNode);
  1110. }
  1111. }
  1112. else
  1113. {
  1114. echo str_repeat('+', $iDepth)." Copying (recursively) the modified node\n";
  1115. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes
  1116. $oDestNode->appendChild($oModifiedNode);
  1117. }
  1118. break;
  1119. default:
  1120. // No change: do nothing
  1121. echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was NOT modified...</p>";
  1122. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Importing the node for future recusrsion if needed
  1123. if ($oChildNode->tagName == 'class')
  1124. {
  1125. echo "<p>".str_repeat('+', $iDepth)."Checking if a subclass of ".$oChildNode->getAttribute('name')." was modified...</p>";
  1126. // classes are always located under the root node
  1127. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  1128. }
  1129. }
  1130. }
  1131. $iDepth--;
  1132. }
  1133. /**
  1134. * Searches on disk in the root directory for module description files
  1135. * and returns an array of MFModules
  1136. * @return array Array of MFModules
  1137. */
  1138. public function FindModules($sSubDirectory = '')
  1139. {
  1140. $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->sRootDir, $sSubDirectory);
  1141. $aResult = array();
  1142. foreach($aAvailableModules as $sId => $aModule)
  1143. {
  1144. $aResult[] = new MFModule($sId, $aModule['root_dir'], $aModule['label']);
  1145. }
  1146. return $aResult;
  1147. }
  1148. /**
  1149. * Produce the PHP files corresponding to the data model
  1150. * @param $bTestOnly bool
  1151. * @return array Array of errors, empty if no error
  1152. */
  1153. public function Compile($bTestOnly)
  1154. {
  1155. return array();
  1156. }
  1157. /**
  1158. * Extracts some nodes from the DOM
  1159. * @param string $sXPath A XPath expression
  1160. * @return DOMNodeList
  1161. */
  1162. public function _priv_GetNodes($sXPath, $oContextNode = null)
  1163. {
  1164. $oXPath = new DOMXPath($this->oDOMDocument);
  1165. if (is_null($oContextNode))
  1166. {
  1167. return $oXPath->query($sXPath);
  1168. }
  1169. else
  1170. {
  1171. return $oXPath->query($sXPath, $oContextNode);
  1172. }
  1173. }
  1174. /**
  1175. * Insert a new node in the DOM
  1176. * @param DOMNode $oNode
  1177. * @param DOMNode $oParentNode
  1178. */
  1179. public function _priv_AddNode(DOMNode $oNode, DOMNode $oParentNode)
  1180. {
  1181. }
  1182. /**
  1183. * Remove a node from the DOM
  1184. * @param DOMNode $oNode
  1185. * @param DOMNode $oParentNode
  1186. */
  1187. public function _priv_RemoveNode(DOMNode $oNode)
  1188. {
  1189. }
  1190. /**
  1191. * Add or modify an attribute of a node
  1192. * @param DOMNode $oNode
  1193. * @param string $sAttributeName
  1194. * @param mixed $atttribueValue
  1195. */
  1196. public function _priv_SetNodeAttribute(DOMNode $oNode, $sAttributeName, $atttribueValue)
  1197. {
  1198. }
  1199. /**
  1200. * Helper to browse the DOM -could be factorized in ModelFactory
  1201. * Returns the node directly under the given node, and that is supposed to be always present and unique
  1202. */
  1203. protected function GetUniqueElement($oDOMNode, $sTagName, $bMustExist = true)
  1204. {
  1205. $oNode = null;
  1206. foreach($oDOMNode->childNodes as $oChildNode)
  1207. {
  1208. if ($oChildNode->nodeName == $sTagName)
  1209. {
  1210. $oNode = $oChildNode;
  1211. break;
  1212. }
  1213. }
  1214. if ($bMustExist && is_null($oNode))
  1215. {
  1216. throw new DOMFormatException('Missing unique tag: '.$sTagName);
  1217. }
  1218. return $oNode;
  1219. }
  1220. /**
  1221. * Helper to browse the DOM -could be factorized in ModelFactory
  1222. * Returns the node directly under the given node, or null is missing
  1223. */
  1224. protected function GetOptionalElement($oDOMNode, $sTagName)
  1225. {
  1226. return $this->GetUniqueElement($oDOMNode, $sTagName, false);
  1227. }
  1228. /**
  1229. * Helper to browse the DOM -could be factorized in ModelFactory
  1230. * Returns the TEXT of the given node (possibly from several subnodes)
  1231. */
  1232. protected function GetNodeText($oNode)
  1233. {
  1234. $sText = '';
  1235. foreach($oNode->childNodes as $oChildNode)
  1236. {
  1237. if ($oChildNode instanceof DOMCharacterData) // Base class of DOMText and DOMCdataSection
  1238. {
  1239. $sText .= $oChildNode->wholeText;
  1240. }
  1241. }
  1242. return $sText;
  1243. }
  1244. /**
  1245. * Helper to browse the DOM -could be factorized in ModelFactory
  1246. * Assumes the given node to be either a text or
  1247. * <items>
  1248. * <item [key]="..."]>value<item>
  1249. * <item [key]="..."]>value<item>
  1250. * </items>
  1251. * where value can be the either a text or an array of items... recursively
  1252. * Returns a PHP array
  1253. */
  1254. public function GetNodeAsArrayOfItems($oNode)
  1255. {
  1256. $oItems = $this->GetOptionalElement($oNode, 'items');
  1257. if ($oItems)
  1258. {
  1259. $res = array();
  1260. foreach($oItems->childNodes as $oItem)
  1261. {
  1262. // When an attribute is missing
  1263. if ($oItem->hasAttribute('key'))
  1264. {
  1265. $key = $oItem->getAttribute('key');
  1266. $res[$key] = $this->GetNodeAsArrayOfItems($oItem);
  1267. }
  1268. else
  1269. {
  1270. $res[] = $this->GetNodeAsArrayOfItems($oItem);
  1271. }
  1272. }
  1273. }
  1274. else
  1275. {
  1276. $res = $this->GetNodeText($oNode);
  1277. }
  1278. return $res;
  1279. }
  1280. }