modelfactory.class.inc.php 40 KB

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