modelfactory.class.inc.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  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 GetDataModelFiles()
  94. {
  95. return $this->aDataModels;
  96. }
  97. /**
  98. * List all classes in this module
  99. */
  100. public function ListClasses()
  101. {
  102. return array();
  103. }
  104. }
  105. class MFWorkspace extends MFModule
  106. {
  107. public function __construct($sRootDir)
  108. {
  109. $this->sId = 'itop-workspace';
  110. $this->sName = 'workspace';
  111. $this->sVersion = '1.0';
  112. $this->sRootDir = $sRootDir;
  113. $this->sLabel = 'Workspace';
  114. $this->aDataModels = array();
  115. $this->aDataModels[] = $this->GetWorkspacePath();
  116. }
  117. public function GetWorkspacePath()
  118. {
  119. return $this->sRootDir.'/workspace.xml';
  120. }
  121. }
  122. /**
  123. * ModelFactoryClass: the representation of a Class (i.e. a PHP class)
  124. * @package ModelFactory
  125. */
  126. class MFClass extends MFItem
  127. {
  128. /**
  129. * List all fields of this class
  130. */
  131. public function ListFields()
  132. {
  133. return array();
  134. }
  135. /**
  136. * List all methods of this class
  137. */
  138. public function ListMethods()
  139. {
  140. return array();
  141. }
  142. /**
  143. * Whether or not the class has a lifecycle
  144. * @return bool
  145. */
  146. public function HasLifeCycle()
  147. {
  148. return true; //TODO implement
  149. }
  150. /**
  151. * Returns the code of the attribute used to store the lifecycle state
  152. * @return string
  153. */
  154. public function GetLifeCycleAttCode()
  155. {
  156. if ($this->HasLifeCycle())
  157. {
  158. }
  159. return '';
  160. }
  161. /**
  162. * List all states of this class
  163. */
  164. public function ListStates()
  165. {
  166. return array();
  167. }
  168. /**
  169. * List all relations of this class
  170. */
  171. public function ListRelations()
  172. {
  173. return array();
  174. }
  175. /**
  176. * List all transitions of this class
  177. */
  178. public function ListTransitions()
  179. {
  180. return array();
  181. }
  182. }
  183. /**
  184. * ModelFactoryField: the representation of a Field (i.e. a property of a class)
  185. * @package ModelFactory
  186. */
  187. class MFField extends MFItem
  188. {
  189. }
  190. /**
  191. * ModelFactoryMethod: the representation of a Method (i.e. a method of a class)
  192. * @package ModelFactory
  193. */
  194. class MFMethod extends MFItem
  195. {
  196. }
  197. /**
  198. * ModelFactoryState: the representation of a state in the life cycle of the class
  199. * @package ModelFactory
  200. */
  201. class MFState extends MFItem
  202. {
  203. }
  204. /**
  205. * ModelFactoryRelation: the representation of a n:n relationship between two classes
  206. * @package ModelFactory
  207. */
  208. class MFRelation extends MFItem
  209. {
  210. }
  211. /**
  212. * ModelFactoryTransition: the representation of a transition between two states in the life cycle of the class
  213. * @package ModelFactory
  214. */
  215. class MFTransition extends MFItem
  216. {
  217. }
  218. /**
  219. * ModelFactory: the class that manages the in-memory representation of the XML MetaModel
  220. * @package ModelFactory
  221. */
  222. class ModelFactory
  223. {
  224. protected $sRootDir;
  225. protected $oDOMDocument;
  226. protected $oClasses;
  227. static protected $aLoadedClasses;
  228. static protected $aWellKnownParents = array('DBObject', 'CMDBObject','cmdbAbstractObject');
  229. static protected $aLoadedModules;
  230. public function __construct($sRootDir)
  231. {
  232. $this->sRootDir = $sRootDir;
  233. $this->oDOMDocument = new DOMDocument('1.0', 'UTF-8');
  234. $this->oClasses = $this->oDOMDocument->CreateElement('classes');
  235. $this->oDOMDocument->AppendChild($this->oClasses);
  236. self::$aLoadedClasses = array();
  237. self::$aLoadedModules = array();
  238. }
  239. public function Dump($oNode = null)
  240. {
  241. if (is_null($oNode))
  242. {
  243. $oNode = $this->oClasses;
  244. }
  245. echo htmlentities($this->oDOMDocument->saveXML($oNode));
  246. }
  247. /**
  248. * Loads the definitions corresponding to the given Module
  249. * @param MFModule $oModule
  250. */
  251. public function LoadModule(MFModule $oModule)
  252. {
  253. $aDataModels = $oModule->GetDataModelFiles();
  254. $sModuleName = $oModule->GetName();
  255. $aClasses = array();
  256. self::$aLoadedModules[] = $oModule;
  257. foreach($aDataModels as $sXmlFile)
  258. {
  259. $oDocument = new DOMDocument('1.0', 'UTF-8');
  260. $oDocument->load($sXmlFile);
  261. $oXPath = new DOMXPath($oDocument);
  262. $oNodeList = $oXPath->query('//*');
  263. foreach($oNodeList as $oNode)
  264. {
  265. $oNode->SetAttribute('_source', $sXmlFile);
  266. }
  267. $oXPath = new DOMXPath($oDocument);
  268. $oNodeList = $oXPath->query('//classes/class');
  269. foreach($oNodeList as $oNode)
  270. {
  271. if ($oNode->hasAttribute('parent'))
  272. {
  273. $sParentClass = $oNode->GetAttribute('parent');
  274. }
  275. else
  276. {
  277. $sParentClass = '';
  278. }
  279. $sClassName = $oNode->GetAttribute('name');
  280. $aClasses[$sClassName] = array('name' => $sClassName, 'parent' => $sParentClass, 'node' => $oNode);
  281. }
  282. }
  283. $index = 1;
  284. do
  285. {
  286. $bNothingLoaded = true;
  287. foreach($aClasses as $sClassName => $aClassData)
  288. {
  289. $sOperation = $aClassData['node']->getAttribute('_operation');
  290. switch($sOperation)
  291. {
  292. case 'created':
  293. case '':
  294. if (in_array($aClassData['parent'], self::$aWellKnownParents))
  295. {
  296. $this->AddClass($aClassData['node'], $sModuleName);
  297. unset($aClasses[$sClassName]);
  298. $bNothingLoaded = false;
  299. }
  300. else if ($this->ClassNameExists($aClassData['parent']))
  301. {
  302. $this->AddClass($aClassData['node'], $sModuleName);
  303. unset($aClasses[$sClassName]);
  304. $bNothingLoaded = false;
  305. }
  306. break;
  307. case 'removed':
  308. unset($aClasses[$sClassName]);
  309. $this->RemoveClass($sClassName);
  310. break;
  311. case 'modified':
  312. unset($aClasses[$sClassName]);
  313. $this->AlterClass($aClassData['node'], $sModuleName);
  314. break;
  315. }
  316. }
  317. $index++;
  318. }
  319. while((count($aClasses)>0) && !$bNothingLoaded);
  320. // The remaining classes have no known parent, let's add them at the root
  321. foreach($aClasses as $sClassName => $aClassData)
  322. {
  323. $sOperation = $aClassData['node']->getAttribute('_operation');
  324. switch($sOperation)
  325. {
  326. case 'created':
  327. case '':
  328. // Add the class as a new root class
  329. $this->AddClass($aClassData['node'], $sModuleName);
  330. $oNewNode = $this->oDOMDocument->ImportNode($aClassData['node']);
  331. break;
  332. case 'removed':
  333. $this->RemoveClass($sClassName);
  334. break;
  335. case 'modified':
  336. //@@TODO Handle the modification of a class here
  337. $this->AlterClass($aClassData['node'], $sModuleName);
  338. break;
  339. }
  340. }
  341. }
  342. function GetLoadedModules($bExcludeWorkspace = true)
  343. {
  344. if ($bExcludeWorkspace)
  345. {
  346. $aModules = array();
  347. foreach(self::$aLoadedModules as $oModule)
  348. {
  349. if (!$oModule instanceof MFWorkspace)
  350. {
  351. $aModules[] = $oModule;
  352. }
  353. }
  354. }
  355. else
  356. {
  357. $aModules = self::$aLoadedModules;
  358. }
  359. return $aModules;
  360. }
  361. /**
  362. * Check if the class specified by the given node already exists in the loaded DOM
  363. * @param DOMNode $oClassNode The node corresponding to the class to load
  364. * @throws Exception
  365. * @return bool True if the class exists, false otherwise
  366. */
  367. protected function ClassExists(DOMNode $oClassNode)
  368. {
  369. if ($oClassNode->hasAttribute('name'))
  370. {
  371. $sClassName = $oClassNode->GetAttribute('name');
  372. }
  373. else
  374. {
  375. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  376. }
  377. return (array_key_exists($sClassName, self::$aLoadedClasses));
  378. }
  379. /**
  380. * Check if the class specified by the given name already exists in the loaded DOM
  381. * @param string $sClassName The node corresponding to the class to load
  382. * @throws Exception
  383. * @return bool True if the class exists, false otherwise
  384. */
  385. protected function ClassNameExists($sClassName)
  386. {
  387. return (array_key_exists($sClassName, self::$aLoadedClasses));
  388. }
  389. /**
  390. * Add the given class to the DOM
  391. * @param DOMNode $oClassNode
  392. * @param string $sModuleName The name of the module in which this class is declared
  393. * @throws Exception
  394. */
  395. protected function AddClass(DOMNode $oClassNode, $sModuleName)
  396. {
  397. if ($oClassNode->hasAttribute('name'))
  398. {
  399. $sClassName = $oClassNode->GetAttribute('name');
  400. }
  401. else
  402. {
  403. throw new Exception('ModelFactory::AddClass: Cannot add a class with no name');
  404. }
  405. if ($this->ClassExists($oClassNode))
  406. {
  407. throw new Exception("ModelFactory::AddClass: Cannot add the already existing class $sClassName");
  408. }
  409. $sParentClass = '';
  410. if ($oClassNode->hasAttribute('parent'))
  411. {
  412. $sParentClass = $oClassNode->GetAttribute('parent');
  413. }
  414. //echo "Adding class: $sClassName, parent: $sParentClass<br/>";
  415. if (!in_array($sParentClass, self::$aWellKnownParents) && $this->ClassNameExists($sParentClass))
  416. {
  417. // The class is a subclass of a class already loaded, add it under
  418. self::$aLoadedClasses[$sClassName] = $this->oDOMDocument->ImportNode($oClassNode, true /* bDeep */);
  419. self::$aLoadedClasses[$sClassName]->SetAttribute('_operation', 'added');
  420. self::$aLoadedClasses[$sClassName]->SetAttribute('_created_in', $sModuleName);
  421. self::$aLoadedClasses[$sParentClass]->AppendChild(self::$aLoadedClasses[$sClassName]);
  422. $bNothingLoaded = false;
  423. }
  424. else if (in_array($sParentClass, self::$aWellKnownParents))
  425. {
  426. // Add the class as a new root class
  427. self::$aLoadedClasses[$sClassName] = $this->oDOMDocument->ImportNode($oClassNode, true /* bDeep */);
  428. self::$aLoadedClasses[$sClassName]->SetAttribute('_operation', 'added');
  429. self::$aLoadedClasses[$sClassName]->SetAttribute('_created_in', $sModuleName);
  430. $this->oClasses->AppendChild(self::$aLoadedClasses[$sClassName]);
  431. }
  432. else
  433. {
  434. throw new Exception("ModelFactory::AddClass: Cannot add the class $sClassName, unknown parent class: $sParentClass");
  435. }
  436. }
  437. /**
  438. * Remove a class from the DOM
  439. * @param string $sClass
  440. * @throws Exception
  441. */
  442. public function RemoveClass($sClass)
  443. {
  444. if (!$this->ClassNameExists($sClass))
  445. {
  446. throw new Exception("ModelFactory::RemoveClass: Cannot remove the non existing class $sClass");
  447. }
  448. self::$aLoadedClasses[$sClass]->SetAttribute('_operation', 'removed');
  449. //TODO: also mark as removed the child classes
  450. }
  451. public function AlterClass(DOMNode $oClassNode, $sModuleName)
  452. {
  453. if ($oClassNode->hasAttribute('name'))
  454. {
  455. $sClassName = $oClassNode->GetAttribute('name');
  456. }
  457. else
  458. {
  459. throw new Exception('ModelFactory::AddClass: Cannot alter a class with no name');
  460. }
  461. if (!$this->ClassExists($oClassNode))
  462. {
  463. throw new Exception("ModelFactory::AddClass: Cannot later the non-existing class $sClassName");
  464. }
  465. $this->_priv_AlterNode(self::$aLoadedClasses[$sClassName], $oClassNode);
  466. }
  467. protected function _priv_AlterNode(DOMNode $oNode, DOMNode $oDeltaNode)
  468. {
  469. foreach ($oDeltaNode->attributes as $sName => $oAttrNode)
  470. {
  471. $oNode->setAttribute($sName, $oAttrNode->value);
  472. }
  473. foreach($oDeltaNode->childNodes as $oChildNode)
  474. {
  475. $sOperation = $oChildNode->getAttribute('_operation');
  476. $sPath = $oChildNode->tagName;
  477. $sName = $oChildNode->getAttribute('name');
  478. if ($sName != '')
  479. {
  480. $sPath .= "[@name='$sName']";
  481. }
  482. switch($sOperation)
  483. {
  484. case 'removed':
  485. $oToRemove = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  486. if ($oToRemove != null)
  487. {
  488. $this->_priv_SetFlag($oToRemove, 'removed');
  489. }
  490. break;
  491. case 'modified':
  492. $oToModify = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  493. if ($oToModify != null)
  494. {
  495. $this->_priv_AlterNode($oToModify, $oChildNode);
  496. }
  497. else
  498. {
  499. throw new Exception("Cannot modify the non-existing node '$sPath' in '".$oNode->getNodePath()."'");
  500. }
  501. break;
  502. case 'replaced':
  503. $oNewNode = $this->oDOMDocument->importNode($oChildNode, true); // Import the node and its child nodes
  504. $oToModify = $this->_priv_GetNodes($sPath, $oNode)->item(0);
  505. $oNode->replaceChild($oNewNode, $oToModify);
  506. break;
  507. case 'created':
  508. $oNewNode = $this->oDOMDocument->importNode($oChildNode);
  509. $oNode->appendChild($oNewNode);
  510. $this->_priv_SetFlag($oNewNode, 'created');
  511. break;
  512. case '':
  513. // Do nothing
  514. }
  515. }
  516. }
  517. /**
  518. * List all classes from the DOM, for a given module
  519. * @param string $sModuleNale
  520. * @param bool $bFlattenLayers
  521. * @throws Exception
  522. */
  523. public function ListClasses($sModuleName, $bFlattenLayers = true)
  524. {
  525. $sXPath = "//class[@_created_in='$sModuleName']";
  526. if ($bFlattenLayers)
  527. {
  528. $sXPath = "//class[@_created_in='$sModuleName' and @_operation!='removed']";
  529. }
  530. return $this->_priv_GetNodes($sXPath);
  531. }
  532. public function GetClass($sClassName, $bFlattenLayers = true)
  533. {
  534. if (!$this->ClassNameExists($sClassName))
  535. {
  536. return null;
  537. }
  538. $oClassNode = self::$aLoadedClasses[$sClassName];
  539. if ($bFlattenLayers)
  540. {
  541. $sOperation = $oClassNode->getAttribute('_operation');
  542. if ($sOperation == 'removed')
  543. {
  544. $oClassNode = null;
  545. }
  546. }
  547. return $oClassNode;
  548. }
  549. /**
  550. * List all classes from the DOM
  551. * @param bool $bFlattenLayers
  552. * @throws Exception
  553. */
  554. public function ListFields(DOMNode $oClassNode, $bFlattenLayers = true)
  555. {
  556. $sXPath = "fields/field";
  557. if ($bFlattenLayers)
  558. {
  559. $sXPath = "fields/field[not(@_operation) or @_operation!='removed']";
  560. }
  561. return $this->_priv_GetNodes($sXPath, $oClassNode);
  562. }
  563. public function AddField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  564. {
  565. $oNewField = $this->oDOMDocument->createElement('field');
  566. $oNewField->setAttribute('name', $sFieldCode);
  567. $this->_priv_AlterField($oNewField, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  568. $oFields = $oClassNode->getElementsByTagName('fields')->item(0);
  569. $oFields->AppendChild($oNewField);
  570. $this->_priv_SetFlag($oNewField, 'created');
  571. }
  572. public function RemoveField(DOMNode $oClassNode, $sFieldCode)
  573. {
  574. $sXPath = "fields/field[@name='$sFieldCode']";
  575. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  576. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  577. {
  578. //@@TODO: if the field was 'created' => then really delete it
  579. $this->_priv_SetFlag($oFieldNodes->item(0), 'removed');
  580. }
  581. }
  582. public function AlterField(DOMNode $oClassNode, $sFieldCode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  583. {
  584. $sXPath = "fields/field[@name='$sFieldCode']";
  585. $oFieldNodes = $this->_priv_GetNodes($sXPath, $oClassNode);
  586. if (is_object($oFieldNodes) && (is_object($oFieldNodes->item(0))))
  587. {
  588. //@@TODO: if the field was 'created' => then let it as 'created'
  589. $this->_priv_SetFlag($oFieldNodes->item(0), 'modified');
  590. $this->_priv_AlterField($oFieldNodes->item(0), $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams);
  591. }
  592. }
  593. protected function _priv_AlterField(DOMNode $oFieldNode, $sFieldType, $sSQL, $defaultValue, $bIsNullAllowed, $aExtraParams)
  594. {
  595. switch($sFieldType)
  596. {
  597. case 'String':
  598. case 'Text':
  599. case 'IPAddress':
  600. case 'EmailAddress':
  601. case 'Blob':
  602. break;
  603. case 'ExternalKey':
  604. $this->_priv_AddFieldAttribute($oFieldNode, 'target_class', $aExtraParams);
  605. // Fall through
  606. case 'HierarchicalKey':
  607. $this->_priv_AddFieldAttribute($oFieldNode, 'on_target_delete', $aExtraParams);
  608. $this->_priv_AddFieldAttribute($oFieldNode, 'filter', $aExtraParams);
  609. break;
  610. case 'ExternalField':
  611. $this->_priv_AddFieldAttribute($oFieldNode, 'extkey_attcode', $aExtraParams);
  612. $this->_priv_AddFieldAttribute($oFieldNode, 'target_attcode', $aExtraParams);
  613. break;
  614. case 'Enum':
  615. $this->_priv_SetFieldValues($oFieldNode, $aExtraParams);
  616. break;
  617. case 'LinkedSetIndirect':
  618. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_remote', $aExtraParams);
  619. // Fall through
  620. case 'LinkedSet':
  621. $this->_priv_AddFieldAttribute($oFieldNode, 'linked_class', $aExtraParams);
  622. $this->_priv_AddFieldAttribute($oFieldNode, 'ext_key_to_me', $aExtraParams);
  623. $this->_priv_AddFieldAttribute($oFieldNode, 'count_min', $aExtraParams);
  624. $this->_priv_AddFieldAttribute($oFieldNode, 'count_max', $aExtraParams);
  625. break;
  626. default:
  627. throw(new Exception('Unsupported type of field: '.$sFieldType));
  628. }
  629. $this->_priv_SetFieldDependencies($oFieldNode, $aExtraParams);
  630. $oFieldNode->setAttribute('type', $sFieldType);
  631. $oFieldNode->setAttribute('sql', $sSQL);
  632. $oFieldNode->setAttribute('default_value', $defaultValue);
  633. $oFieldNode->setAttribute('is_null_alllowed', $bIsNullAllowed ? 'true' : 'false');
  634. }
  635. protected function _priv_AddFieldAttribute(DOMNode $oFieldNode, $sAttributeCode, $aExtraParams, $bMandatory = false)
  636. {
  637. $value = array_key_exists($sAttributeCode, $aExtraParams) ? $aExtraParams[$sAttributeCode] : '';
  638. if (($value == '') && (!$bMandatory)) return;
  639. $oFieldNode->setAttribute($sAttributeCode, $value);
  640. }
  641. protected function _priv_SetFieldDependencies($oFieldNode, $aExtraParams)
  642. {
  643. $aDeps = array_key_exists('dependencies', $aExtraParams) ? $aExtraParams['dependencies'] : '';
  644. $oDependencies = $oFieldNode->getElementsByTagName('dependencies')->item(0);
  645. // No dependencies before, and no dependencies to add, exit
  646. if (($oDependencies == null) && ($aDeps == '')) return;
  647. // Remove the previous dependencies
  648. $oFieldNode->removeChild($oDependencies);
  649. // If no dependencies, exit
  650. if ($aDeps == '') return;
  651. // Build the new list of dependencies
  652. $oDependencies = $this->oDOMDocument->createElement('dependencies');
  653. foreach($aDeps as $sAttCode)
  654. {
  655. $oDep = $this->oDOMDocument->createElement('attribute');
  656. $oDep->setAttribute('name', $sAttCode);
  657. $oDependencies->addChild($oDep);
  658. }
  659. $oFieldNode->addChild($oDependencies);
  660. }
  661. protected function _priv_SetFieldValues($oFieldNode, $aExtraParams)
  662. {
  663. $aVals = array_key_exists('values', $aExtraParams) ? $aExtraParams['values'] : '';
  664. $oValues = $oFieldNode->getElementByTagName('values')->item(0);
  665. // No dependencies before, and no dependencies to add, exit
  666. if (($oValues == null) && ($aVals == '')) return;
  667. // Remove the previous dependencies
  668. $oFieldNode->removeChild($oValues);
  669. // If no dependencies, exit
  670. if ($aVals == '') return;
  671. // Build the new list of dependencies
  672. $oValues = $this->oDOMDocument->createElement('values');
  673. foreach($aVals as $sValue)
  674. {
  675. $oVal = $this->oDOMDocument->createElement('value', $sValue);
  676. $oValues->addChild($oVal);
  677. }
  678. $oFieldNode->addChild($oValues);
  679. }
  680. protected function _priv_SetFlag($oNode, $sFlagValue)
  681. {
  682. $oNode->setAttribute('_operation', $sFlagValue);
  683. if ($oNode->tagName != 'class')
  684. {
  685. $this->_priv_SetFlag($oNode->parentNode, 'modified');
  686. }
  687. }
  688. /**
  689. * List all transitions from a given state
  690. * @param DOMNode $oStateNode The state
  691. * @param bool $bFlattenLayers
  692. * @throws Exception
  693. */
  694. public function ListTransitions(DOMNode $oStateNode, $bFlattenLayers = true)
  695. {
  696. $sXPath = "transitions/transition";
  697. if ($bFlattenLayers)
  698. {
  699. //$sXPath = "transitions/transition[@_operation!='removed']";
  700. }
  701. return $this->_priv_GetNodes($sXPath, $oStateNode);
  702. }
  703. /**
  704. * List all states of a given class
  705. * @param DOMNode $oClassNode The class
  706. * @param bool $bFlattenLayers
  707. * @throws Exception
  708. */
  709. public function ListStates(DOMNode $oClassNode, $bFlattenLayers = true)
  710. {
  711. $sXPath = "lifecycle/states/state";
  712. if ($bFlattenLayers)
  713. {
  714. //$sXPath = "lifecycle/states/state[@_operation!='removed']";
  715. }
  716. return $this->_priv_GetNodes($sXPath, $oClassNode);
  717. }
  718. /**
  719. * List Zlists from the DOM for a given class
  720. * @param bool $bFlattenLayers
  721. * @throws Exception
  722. */
  723. public function ListZLists(DOMNode $oClassNode, $bFlattenLayers = true)
  724. {
  725. // Not yet implemented !!!
  726. return array();
  727. }
  728. public function ApplyChanges()
  729. {
  730. $oNodes = $this->ListChanges();
  731. foreach($oNodes as $oClassNode)
  732. {
  733. $sOperation = $oClassNode->GetAttribute('_operation');
  734. switch($sOperation)
  735. {
  736. case 'added':
  737. case 'modified':
  738. // marked as added or modified, just reset the flag
  739. $oClassNode->setAttribute('_operation', '');
  740. break;
  741. case 'removed':
  742. // marked as deleted, let's remove the node from the tree
  743. $oParent = $oClassNode->parentNode;
  744. $sClass = $oClassNode->GetAttribute('name');
  745. echo "Calling removeChild...<br/>";
  746. $oParent->removeChild($oClassNode);
  747. unset(self::$aLoadedClasses[$sClass]);
  748. break;
  749. }
  750. }
  751. }
  752. public function ListChanges()
  753. {
  754. $sXPath = "//*[@_operation!='']";
  755. return $this->_priv_GetNodes($sXPath);
  756. }
  757. /**
  758. * Get the text/XML version of the delta
  759. */
  760. public function GetDelta()
  761. {
  762. $oDelta = new DOMDocument('1.0', 'UTF-8');
  763. $oRootNode = $oDelta->createElement('classes');
  764. $oDelta->appendChild($oRootNode);
  765. $this->_priv_ImportModifiedChildren($oDelta, $oRootNode, $this->oDOMDocument->firstChild);
  766. //file_put_contents($sXMLDestPath, $oDelta->saveXML());
  767. return $oDelta->saveXML();
  768. }
  769. protected function _priv_ImportModifiedChildren(DOMDocument $oDoc, DOMNode $oDestNode, DOMNode $oNode)
  770. {
  771. static $iDepth = 0;
  772. $iDepth++;
  773. foreach($oNode->childNodes as $oChildNode)
  774. {
  775. $sOperation = $oChildNode->getAttribute('_operation');
  776. switch($sOperation)
  777. {
  778. case 'removed':
  779. $oDeletedNode = $oDoc->importNode($oChildNode->cloneNode(false)); // Copies all the node's attributes, but NOT the child nodes
  780. $oDeletedNode->removeAttribute('_source');
  781. if ($oDeletedNode->tagName == 'class')
  782. {
  783. // classes are always located under the root node
  784. $oDoc->firstChild->appendChild($oDeletedNode);
  785. }
  786. else
  787. {
  788. $oDestNode->appendChild($oDeletedNode);
  789. }
  790. //echo "<p>".str_repeat('+', $iDepth).$oChildNode->getAttribute('name')." was removed...</p>";
  791. break;
  792. case 'modified':
  793. case 'created':
  794. //echo "<p>".str_repeat('+', $iDepth).$oChildNode->tagName.':'.$oChildNode->getAttribute('name')." was modified...</p>";
  795. $oModifiedNode = $oDoc->importNode($oChildNode->cloneNode(false)); // Copies all the node's attributes, but NOT the child nodes
  796. $oModifiedNode->removeAttribute('_source');
  797. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  798. if ($oModifiedNode->tagName == 'class')
  799. {
  800. // classes are always located under the root node
  801. $oDoc->firstChild->appendChild($oModifiedNode);
  802. }
  803. else
  804. {
  805. $oDestNode->appendChild($oModifiedNode);
  806. }
  807. break;
  808. default:
  809. // No change: search if there is not a modified child class
  810. $oModifiedNode = $oDoc->importNode($oChildNode->cloneNode(false)); // Copies all the node's attributes, but NOT the child nodes
  811. $oModifiedNode->removeAttribute('_source');
  812. if ($oChildNode->tagName == 'class')
  813. {
  814. //echo "<p>".str_repeat('+', $iDepth)."Checking if a subclass of ".$oChildNode->getAttribute('name')." was modified...</p>";
  815. // classes are always located under the root node
  816. $this->_priv_ImportModifiedChildren($oDoc, $oModifiedNode, $oChildNode);
  817. }
  818. }
  819. }
  820. $iDepth--;
  821. }
  822. /**
  823. * Searches on disk in the root directory for module description files
  824. * and returns an array of MFModules
  825. * @return array Array of MFModules
  826. */
  827. public function FindModules($sSubDirectory = '')
  828. {
  829. $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->sRootDir, $sSubDirectory);
  830. $aResult = array();
  831. foreach($aAvailableModules as $sId => $aModule)
  832. {
  833. $aResult[] = new MFModule($sId, $aModule['root_dir'], $aModule['label']);
  834. }
  835. return $aResult;
  836. }
  837. /**
  838. * Produce the PHP files corresponding to the data model
  839. * @param $bTestOnly bool
  840. * @return array Array of errors, empty if no error
  841. */
  842. public function Compile($bTestOnly)
  843. {
  844. return array();
  845. }
  846. /**
  847. * Extracts some nodes from the DOM
  848. * @param string $sXPath A XPath expression
  849. * @return DOMNodeList
  850. */
  851. public function _priv_GetNodes($sXPath, $oContextNode = null)
  852. {
  853. $oXPath = new DOMXPath($this->oDOMDocument);
  854. if (is_null($oContextNode))
  855. {
  856. return $oXPath->query($sXPath);
  857. }
  858. else
  859. {
  860. return $oXPath->query($sXPath, $oContextNode);
  861. }
  862. }
  863. /**
  864. * Insert a new node in the DOM
  865. * @param DOMNode $oNode
  866. * @param DOMNode $oParentNode
  867. */
  868. public function _priv_AddNode(DOMNode $oNode, DOMNode $oParentNode)
  869. {
  870. }
  871. /**
  872. * Remove a node from the DOM
  873. * @param DOMNode $oNode
  874. * @param DOMNode $oParentNode
  875. */
  876. public function _priv_RemoveNode(DOMNode $oNode)
  877. {
  878. }
  879. /**
  880. * Add or modify an attribute of a node
  881. * @param DOMNode $oNode
  882. * @param string $sAttributeName
  883. * @param mixed $atttribueValue
  884. */
  885. public function _priv_SetNodeAttribute(DOMNode $oNode, $sAttributeName, $atttribueValue)
  886. {
  887. }
  888. }