modelfactory.class.inc.php 36 KB

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