modelfactory.class.inc.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383
  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 GetField($sClassName, $sAttCode, $bFlattenLayers = true)
  666. {
  667. if (!$this->ClassNameExists($sClassName))
  668. {
  669. return null;
  670. }
  671. $oClassNode = self::$aLoadedClasses[$sClassName];
  672. if ($bFlattenLayers)
  673. {
  674. $sOperation = $oClassNode->getAttribute('_operation');
  675. if ($sOperation == 'removed')
  676. {
  677. $oClassNode = null;
  678. }
  679. }
  680. $sXPath = "fields/field[@name='$sAttCode']";
  681. if ($bFlattenLayers)
  682. {
  683. $sXPath = "fields/field[(@name='$sAttCode' and (not(@_operation) or @_operation!='removed'))]";
  684. }
  685. $oFieldNode = $this->_priv_GetNodes($sXPath, $oClassNode)->item(0);
  686. if (($oFieldNode == null) && ($oClassNode->getAttribute('parent') != ''))
  687. {
  688. return $this->GetField($oClassNode->getAttribute('parent'), $sAttCode, $bFlattenLayers);
  689. }
  690. return $oFieldNode;
  691. }
  692. /**
  693. * List all classes from the DOM
  694. * @param bool $bFlattenLayers
  695. * @throws Exception
  696. */
  697. public function ListFields(DOMNode $oClassNode, $bFlattenLayers = true)
  698. {
  699. $sXPath = "fields/field";
  700. if ($bFlattenLayers)
  701. {
  702. $sXPath = "fields/field[not(@_operation) or @_operation!='removed']";
  703. }
  704. return $this->_priv_GetNodes($sXPath, $oClassNode);
  705. }
  706. public function AddField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  707. {
  708. $oNewField = $this->oDOMDocument->createElement('field');
  709. $oNewField->setAttribute('name', $sFieldCode);
  710. $this->_priv_AlterField($oNewField, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  711. $oFields = $oClassNode->getElementsByTagName('fields')->item(0);
  712. $oFields->AppendChild($oNewField);
  713. $this->_priv_SetFlag($oNewField, 'added');
  714. }
  715. public function RemoveField(DOMNode $oClassNode, $sFieldCode)
  716. {
  717. $sXPath = "fields/field[@name='$sFieldCode']";
  718. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  719. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  720. {
  721. $oFieldNode = $oFieldNodes->item(0);
  722. $sOpCode = $oFieldNode->getAttribute('_operation');
  723. if ($oFieldNode->getAttribute('_operation') == 'added')
  724. {
  725. $oFieldNode->parentNode->removeChild($oFieldNode);
  726. }
  727. else
  728. {
  729. $this->_priv_SetFlag($oFieldNode, 'removed');
  730. }
  731. }
  732. }
  733. public function AlterField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  734. {
  735. $sXPath = "fields/field[@name='$sFieldCode']";
  736. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  737. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  738. {
  739. $oFieldNode = $oFieldNodes->item(0);
  740. //@@TODO: if the field was 'added' => then let it as 'added'
  741. $sOpCode = $oFieldNode->getAttribute('_operation');
  742. switch($sOpCode)
  743. {
  744. case 'added':
  745. case 'modified':
  746. // added or modified, let it as it is
  747. break;
  748. default:
  749. $this->_priv_SetFlag($oFieldNodes->item(0), 'modified');
  750. }
  751. $this->_priv_AlterField($oFieldNodes->item(0), $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  752. }
  753. }
  754. protected function _priv_AlterField(DOMNode $oFieldNode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  755. {
  756. switch($sFieldType)
  757. {
  758. case 'Blob':
  759. case 'Boolean':
  760. case 'CaseLog':
  761. case 'Deadline':
  762. case 'Duration':
  763. case 'EmailAddress':
  764. case 'EncryptedString':
  765. case 'HTML':
  766. case 'IPAddress':
  767. case 'LongText':
  768. case 'OQL':
  769. case 'OneWayPassword':
  770. case 'Password':
  771. case 'Percentage':
  772. case 'String':
  773. case 'Text':
  774. case 'Text':
  775. case 'TemplateHTML':
  776. case 'TemplateString':
  777. case 'TemplateText':
  778. case 'URL':
  779. case 'Date':
  780. case 'DateTime':
  781. case 'Decimal':
  782. case 'Integer':
  783. break;
  784. case 'ExternalKey':
  785. $this->_priv_AddFieldAttribute($oFieldNode, 'target_class', $aExtraParams);
  786. // Fall through
  787. case 'HierarchicalKey':
  788. $this->_priv_AddFieldAttribute($oFieldNode, 'on_target_delete', $aExtraParams);
  789. $this->_priv_AddFieldAttribute($oFieldNode, 'filter', $aExtraParams);
  790. break;
  791. case 'ExternalField':
  792. $this->_priv_AddFieldAttribute($oFieldNode, 'extkey_attcode', $aExtraParams);
  793. $this->_priv_AddFieldAttribute($oFieldNode, 'target_attcode', $aExtraParams);
  794. break;
  795. case 'Enum':
  796. $this->_priv_SetFieldValues($oFieldNode, $aExtraParams);
  797. break;
  798. case 'LinkedSetIndirect':
  799. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_remote', $aExtraParams);
  800. // Fall through
  801. case 'LinkedSet':
  802. $this->_priv_AddFieldAttribute($oFieldNode, 'linked_class', $aExtraParams);
  803. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_me', $aExtraParams);
  804. $this->_priv_AddFieldAttribute($oFieldNode, 'count_min', $aExtraParams);
  805. $this->_priv_AddFieldAttribute($oFieldNode, 'count_max', $aExtraParams);
  806. break;
  807. default:
  808. throw(new Exception('Unsupported type of field: '.$sFieldType));
  809. }
  810. $this->_priv_SetFieldDependencies($oFieldNode, $aExtraParams);
  811. $oFieldNode->setAttribute('type', $sFieldType);
  812. $oFieldNode->setAttribute('sql', $sSQL);
  813. $oFieldNode->setAttribute('default_value', $defaultValue);
  814. $oFieldNode->setAttribute('is_null_alllowed', $bIsNullAllowed ? 'true' : 'false');
  815. }
  816. protected function _priv_AddFieldAttribute(DOMNode $oFieldNode, $sAttributeCode, $aExtraParams, $bMandatory = false)
  817. {
  818. $value = array_key_exists($sAttributeCode, $aExtraParams) ? $aExtraParams[$sAttributeCode] : '';
  819. if (($value == '') && (!$bMandatory)) return;
  820. $oFieldNode->setAttribute($sAttributeCode, $value);
  821. }
  822. protected function _priv_SetFieldDependencies($oFieldNode, $aExtraParams)
  823. {
  824. $aDeps = array_key_exists('dependencies', $aExtraParams) ? $aExtraParams['dependencies'] : '';
  825. $oDependencies = $oFieldNode->getElementsByTagName('dependencies')->item(0);
  826. // No dependencies before, and no dependencies to add, exit
  827. if (($oDependencies == null) && ($aDeps == '')) return;
  828. // Remove the previous dependencies
  829. $oFieldNode->removeChild($oDependencies);
  830. // If no dependencies, exit
  831. if ($aDeps == '') return;
  832. // Build the new list of dependencies
  833. $oDependencies = $this->oDOMDocument->createElement('dependencies');
  834. foreach($aDeps as $sAttCode)
  835. {
  836. $oDep = $this->oDOMDocument->createElement('attribute');
  837. $oDep->setAttribute('name', $sAttCode);
  838. $oDependencies->addChild($oDep);
  839. }
  840. $oFieldNode->addChild($oDependencies);
  841. }
  842. protected function _priv_SetFieldValues($oFieldNode, $aExtraParams)
  843. {
  844. $aVals = array_key_exists('values', $aExtraParams) ? $aExtraParams['values'] : '';
  845. $oValues = $oFieldNode->getElementsByTagName('values')->item(0);
  846. // No dependencies before, and no dependencies to add, exit
  847. if (($oValues == null) && ($aVals == '')) return;
  848. // Remove the previous dependencies
  849. $oFieldNode->removeChild($oValues);
  850. // If no dependencies, exit
  851. if ($aVals == '') return;
  852. // Build the new list of dependencies
  853. $oValues = $this->oDOMDocument->createElement('values');
  854. foreach($aVals as $sValue)
  855. {
  856. $oVal = $this->oDOMDocument->createElement('value', $sValue);
  857. $oValues->appendChild($oVal);
  858. }
  859. $oFieldNode->appendChild($oValues);
  860. }
  861. public function SetPresentation(DOMNode $oClassNode, $sPresentationCode, $aPresentation)
  862. {
  863. $oPresentation = $oClassNode->getElementsByTagName('presentation')->item(0);
  864. if (!is_object($oPresentation))
  865. {
  866. $oPresentation = $this->oDOMDocument->createElement('presentation');
  867. $oClassNode->appendChild($oPresentation);
  868. }
  869. $oZlist = $oPresentation->getElementsByTagName($sPresentationCode)->item(0);
  870. if (is_object($oZlist))
  871. {
  872. // Remove the previous Zlist
  873. $oPresentation->removeChild($oZlist);
  874. }
  875. // Create the ZList anew
  876. $oZlist = $this->oDOMDocument->createElement($sPresentationCode);
  877. $oPresentation->appendChild($oZlist);
  878. $this->AddZListItem($oZlist, $aPresentation);
  879. $this->_priv_SetFlag($oZlist, 'replaced');
  880. }
  881. protected function AddZListItem($oXMLNode, $value)
  882. {
  883. if (is_array($value))
  884. {
  885. $oXmlItems = $this->oDOMDocument->CreateElement('items');
  886. $oXMLNode->appendChild($oXmlItems);
  887. foreach($value as $key => $item)
  888. {
  889. $oXmlItem = $this->oDOMDocument->CreateElement('item');
  890. $oXmlItems->appendChild($oXmlItem);
  891. if (is_string($key))
  892. {
  893. $oXmlItem->SetAttribute('key', $key);
  894. }
  895. $this->AddZListItem($oXmlItem, $item);
  896. }
  897. }
  898. else
  899. {
  900. $oXmlText = $this->oDOMDocument->CreateTextNode((string) $value);
  901. $oXMLNode->appendChild($oXmlText);
  902. }
  903. }
  904. public function _priv_SetFlag($oNode, $sFlagValue)
  905. {
  906. $sPreviousFlag = $oNode->getAttribute('_operation');
  907. if ($sPreviousFlag == 'added')
  908. {
  909. // Do nothing ??
  910. }
  911. else
  912. {
  913. $oNode->setAttribute('_operation', $sFlagValue);
  914. }
  915. if ($oNode->tagName != 'class')
  916. {
  917. $this->_priv_SetFlag($oNode->parentNode, 'modified');
  918. }
  919. }
  920. /**
  921. * List all transitions from a given state
  922. * @param DOMNode $oStateNode The state
  923. * @param bool $bFlattenLayers
  924. * @throws Exception
  925. */
  926. public function ListTransitions(DOMNode $oStateNode, $bFlattenLayers = true)
  927. {
  928. $sXPath = "transitions/transition";
  929. if ($bFlattenLayers)
  930. {
  931. //$sXPath = "transitions/transition[@_operation!='removed']";
  932. }
  933. return $this->_priv_GetNodes($sXPath, $oStateNode);
  934. }
  935. /**
  936. * List all states of a given class
  937. * @param DOMNode $oClassNode The class
  938. * @param bool $bFlattenLayers
  939. * @throws Exception
  940. */
  941. public function ListStates(DOMNode $oClassNode, $bFlattenLayers = true)
  942. {
  943. $sXPath = "lifecycle/states/state";
  944. if ($bFlattenLayers)
  945. {
  946. //$sXPath = "lifecycle/states/state[@_operation!='removed']";
  947. }
  948. return $this->_priv_GetNodes($sXPath, $oClassNode);
  949. }
  950. /**
  951. * List Zlists from the DOM for a given class
  952. * @param bool $bFlattenLayers
  953. * @throws Exception
  954. */
  955. public function ListZLists(DOMNode $oClassNode, $bFlattenLayers = true)
  956. {
  957. // Not yet implemented !!!
  958. return array();
  959. }
  960. public function ApplyChanges()
  961. {
  962. $oNodes = $this->ListChanges();
  963. foreach($oNodes as $oClassNode)
  964. {
  965. $sOperation = $oClassNode->GetAttribute('_operation');
  966. switch($sOperation)
  967. {
  968. case 'added':
  969. case 'modified':
  970. // marked as added or modified, just reset the flag
  971. $oClassNode->setAttribute('_operation', '');
  972. break;
  973. case 'removed':
  974. // marked as deleted, let's remove the node from the tree
  975. $oParent = $oClassNode->parentNode;
  976. $sClass = $oClassNode->GetAttribute('name');
  977. echo "Calling removeChild...<br/>";
  978. $oParent->removeChild($oClassNode);
  979. unset(self::$aLoadedClasses[$sClass]);
  980. break;
  981. }
  982. }
  983. }
  984. public function ListChanges()
  985. {
  986. $sXPath = "//*[@_operation!='']";
  987. return $this->_priv_GetNodes($sXPath);
  988. }
  989. /**
  990. * Get the text/XML version of the delta
  991. */
  992. public function GetDelta()
  993. {
  994. $oDelta = new DOMDocument('1.0', 'UTF-8');
  995. $oRootNode = $oDelta->createElement('classes');
  996. $oDelta->appendChild($oRootNode);
  997. $this->_priv_ImportModifiedChildren($oDelta, $oRootNode, $this->oDOMDocument->firstChild);
  998. //file_put_contents($sXMLDestPath, $oDelta->saveXML());
  999. return $oDelta->saveXML();
  1000. }
  1001. protected function _priv_ImportModifiedChildren(DOMDocument $oDoc, DOMNode $oDestNode, DOMNode $oNode)
  1002. {
  1003. static $iDepth = 0;
  1004. $iDepth++;
  1005. foreach($oNode->childNodes as $oChildNode)
  1006. {
  1007. $sNodeName = $oChildNode->nodeName;
  1008. $sNodeValue = $oChildNode->nodeValue;
  1009. if (!$oChildNode instanceof DOMElement)
  1010. {
  1011. $sName = '$$';
  1012. $sOperation = $oChildNode->parentNode->getAttribute('_operation');
  1013. }
  1014. else
  1015. {
  1016. $sName = $oChildNode->getAttribute('name');;
  1017. $sOperation = $oChildNode->getAttribute('_operation');
  1018. }
  1019. //echo str_repeat('+', $iDepth)." $sNodeName [$sName], operation: $sOperation\n";
  1020. switch($sOperation)
  1021. {
  1022. case 'removed':
  1023. $oDeletedNode = $oDoc->importNode($oChildNode->cloneNode(false)); // Copies all the node's attributes, but NOT the child nodes
  1024. $oDeletedNode->removeAttribute('_source');
  1025. if ($oDeletedNode->tagName == 'class')
  1026. {
  1027. // classes are always located under the root node
  1028. $oDoc->firstChild->appendChild($oDeletedNode);
  1029. }
  1030. else
  1031. {
  1032. $oDestNode->appendChild($oDeletedNode);
  1033. }
  1034. //echo "<p>".str_repeat('+', $iDepth).$oChildNode->getAttribute('name')." was removed...</p>";
  1035. break;
  1036. case 'added':
  1037. //echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was created...</p>";
  1038. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes as well
  1039. if ($oChildNode instanceof DOMElement)
  1040. {
  1041. $oModifiedNode->removeAttribute('_source');
  1042. if ($oModifiedNode->tagName == 'class')
  1043. {
  1044. // classes are always located under the root node
  1045. $oDoc->firstChild->appendChild($oModifiedNode);
  1046. }
  1047. else
  1048. {
  1049. $oDestNode->appendChild($oModifiedNode);
  1050. }
  1051. }
  1052. else
  1053. {
  1054. $oDestNode->appendChild($oModifiedNode);
  1055. }
  1056. break;
  1057. case 'replaced':
  1058. //echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was replaced...</p>";
  1059. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes as well
  1060. if ($oChildNode instanceof DOMElement)
  1061. {
  1062. $oModifiedNode->removeAttribute('_source');
  1063. }
  1064. $oDestNode->appendChild($oModifiedNode);
  1065. break;
  1066. case 'modified':
  1067. //echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was modified...</p>";
  1068. if ($oChildNode instanceof DOMElement)
  1069. {
  1070. //echo str_repeat('+', $iDepth)." Copying (NON recursively) the modified node\n";
  1071. $oModifiedNode = $oDoc->importNode($oChildNode, false); // Copies all the node's attributes, but NOT the child nodes
  1072. $oModifiedNode->removeAttribute('_source');
  1073. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  1074. if ($oModifiedNode->tagName == 'class')
  1075. {
  1076. // classes are always located under the root node
  1077. $oDoc->firstChild->appendChild($oModifiedNode);
  1078. }
  1079. else
  1080. {
  1081. $oDestNode->appendChild($oModifiedNode);
  1082. }
  1083. }
  1084. else
  1085. {
  1086. //echo str_repeat('+', $iDepth)." Copying (recursively) the modified node\n";
  1087. $oModifiedNode = $oDoc->importNode($oChildNode, true); // Copies all the node's attributes, and the child nodes
  1088. $oDestNode->appendChild($oModifiedNode);
  1089. }
  1090. break;
  1091. default:
  1092. // No change: search if there is not a modified child class
  1093. $oModifiedNode = $oDoc->importNode($oChildNode->cloneNode(false)); // Copies all the node's attributes, but NOT the child nodes
  1094. //echo str_repeat('+', $iDepth)." Importing (NON recursively) the modified node\n";
  1095. if ($oChildNode instanceof DOMElement)
  1096. {
  1097. $oModifiedNode->removeAttribute('_source');
  1098. }
  1099. }
  1100. if ($oChildNode->tagName == 'class')
  1101. {
  1102. //echo "<p>".str_repeat('+', $iDepth)."Checking if a subclass of ".$oChildNode->getAttribute('name')." was modified...</p>";
  1103. // classes are always located under the root node
  1104. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  1105. }
  1106. }
  1107. $iDepth--;
  1108. }
  1109. /**
  1110. * Searches on disk in the root directory for module description files
  1111. * and returns an array of MFModules
  1112. * @return array Array of MFModules
  1113. */
  1114. public function FindModules($sSubDirectory = '')
  1115. {
  1116. $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->sRootDir, $sSubDirectory);
  1117. $aResult = array();
  1118. foreach($aAvailableModules as $sId => $aModule)
  1119. {
  1120. $aResult[] = new MFModule($sId, $aModule['root_dir'], $aModule['label']);
  1121. }
  1122. return $aResult;
  1123. }
  1124. /**
  1125. * Produce the PHP files corresponding to the data model
  1126. * @param $bTestOnly bool
  1127. * @return array Array of errors, empty if no error
  1128. */
  1129. public function Compile($bTestOnly)
  1130. {
  1131. return array();
  1132. }
  1133. /**
  1134. * Extracts some nodes from the DOM
  1135. * @param string $sXPath A XPath expression
  1136. * @return DOMNodeList
  1137. */
  1138. public function _priv_GetNodes($sXPath, $oContextNode = null)
  1139. {
  1140. $oXPath = new DOMXPath($this->oDOMDocument);
  1141. if (is_null($oContextNode))
  1142. {
  1143. return $oXPath->query($sXPath);
  1144. }
  1145. else
  1146. {
  1147. return $oXPath->query($sXPath, $oContextNode);
  1148. }
  1149. }
  1150. /**
  1151. * Insert a new node in the DOM
  1152. * @param DOMNode $oNode
  1153. * @param DOMNode $oParentNode
  1154. */
  1155. public function _priv_AddNode(DOMNode $oNode, DOMNode $oParentNode)
  1156. {
  1157. }
  1158. /**
  1159. * Remove a node from the DOM
  1160. * @param DOMNode $oNode
  1161. * @param DOMNode $oParentNode
  1162. */
  1163. public function _priv_RemoveNode(DOMNode $oNode)
  1164. {
  1165. }
  1166. /**
  1167. * Add or modify an attribute of a node
  1168. * @param DOMNode $oNode
  1169. * @param string $sAttributeName
  1170. * @param mixed $atttribueValue
  1171. */
  1172. public function _priv_SetNodeAttribute(DOMNode $oNode, $sAttributeName, $atttribueValue)
  1173. {
  1174. }
  1175. /**
  1176. * Helper to browse the DOM -could be factorized in ModelFactory
  1177. * Returns the node directly under the given node, and that is supposed to be always present and unique
  1178. */
  1179. protected function GetUniqueElement($oDOMNode, $sTagName, $bMustExist = true)
  1180. {
  1181. $oNode = null;
  1182. foreach($oDOMNode->childNodes as $oChildNode)
  1183. {
  1184. if ($oChildNode->nodeName == $sTagName)
  1185. {
  1186. $oNode = $oChildNode;
  1187. break;
  1188. }
  1189. }
  1190. if ($bMustExist && is_null($oNode))
  1191. {
  1192. throw new DOMFormatException('Missing unique tag: '.$sTagName);
  1193. }
  1194. return $oNode;
  1195. }
  1196. /**
  1197. * Helper to browse the DOM -could be factorized in ModelFactory
  1198. * Returns the node directly under the given node, or null is missing
  1199. */
  1200. protected function GetOptionalElement($oDOMNode, $sTagName)
  1201. {
  1202. return $this->GetUniqueElement($oDOMNode, $sTagName, false);
  1203. }
  1204. /**
  1205. * Helper to browse the DOM -could be factorized in ModelFactory
  1206. * Returns the TEXT of the given node (possibly from several subnodes)
  1207. */
  1208. protected function GetNodeText($oNode)
  1209. {
  1210. $sText = '';
  1211. foreach($oNode->childNodes as $oChildNode)
  1212. {
  1213. if ($oChildNode instanceof DOMCharacterData) // Base class of DOMText and DOMCdataSection
  1214. {
  1215. $sText .= $oChildNode->wholeText;
  1216. }
  1217. }
  1218. return $sText;
  1219. }
  1220. /**
  1221. * Helper to browse the DOM -could be factorized in ModelFactory
  1222. * Assumes the given node to be either a text or
  1223. * <items>
  1224. * <item [key]="..."]>value<item>
  1225. * <item [key]="..."]>value<item>
  1226. * </items>
  1227. * where value can be the either a text or an array of items... recursively
  1228. * Returns a PHP array
  1229. */
  1230. public function GetNodeAsArrayOfItems($oNode)
  1231. {
  1232. $oItems = $this->GetOptionalElement($oNode, 'items');
  1233. if ($oItems)
  1234. {
  1235. $res = array();
  1236. foreach($oItems->childNodes as $oItem)
  1237. {
  1238. // When an attribute is missing
  1239. if ($oItem->hasAttribute('key'))
  1240. {
  1241. $key = $oItem->getAttribute('key');
  1242. $res[$key] = $this->GetNodeAsArrayOfItems($oItem);
  1243. }
  1244. else
  1245. {
  1246. $res[] = $this->GetNodeAsArrayOfItems($oItem);
  1247. }
  1248. }
  1249. }
  1250. else
  1251. {
  1252. $res = $this->GetNodeText($oNode);
  1253. }
  1254. return $res;
  1255. }
  1256. }