runtimeenv.class.inc.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  1. <?php
  2. // Copyright (C) 2010-2017 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Manage a runtime environment
  20. *
  21. * @copyright Copyright (C) 2010-2017 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. require_once(APPROOT."setup/modulediscovery.class.inc.php");
  25. require_once(APPROOT.'setup/modelfactory.class.inc.php');
  26. require_once(APPROOT.'setup/compiler.class.inc.php');
  27. require_once(APPROOT.'setup/extensionsmap.class.inc.php');
  28. require_once(APPROOT.'core/metamodel.class.php');
  29. define ('MODULE_ACTION_OPTIONAL', 1);
  30. define ('MODULE_ACTION_MANDATORY', 2);
  31. define ('MODULE_ACTION_IMPOSSIBLE', 3);
  32. define ('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application
  33. define ('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel
  34. class RunTimeEnvironment
  35. {
  36. protected $sTargetEnv;
  37. /**
  38. * Extensions map of the source environment
  39. * @var iTopExtensionsMap
  40. */
  41. protected $oExtensionsMap;
  42. public function __construct($sEnvironment = 'production')
  43. {
  44. $this->sTargetEnv = $sEnvironment;
  45. $this->oExtensionsMap = null;
  46. }
  47. /**
  48. * Callback function for logging the queries run by the setup.
  49. * According to the documentation the function must be defined before passing it to call_user_func...
  50. * @param string $sQuery
  51. * @param float $fDuration
  52. * @return void
  53. */
  54. public function LogQueryCallback($sQuery, $fDuration)
  55. {
  56. $this->log_info(sprintf('%.3fs - query: %s ', $fDuration, $sQuery));
  57. }
  58. /**
  59. * Helper function to initialize the ORM and load the data model
  60. * from the given file
  61. * @param $oConfig object The configuration (volatile, not necessarily already on disk)
  62. * @param $bModelOnly boolean Whether or not to allow loading a data model with no corresponding DB
  63. * @return none
  64. */
  65. public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false)
  66. {
  67. require_once(APPROOT.'/core/log.class.inc.php');
  68. require_once(APPROOT.'/core/kpi.class.inc.php');
  69. require_once(APPROOT.'/core/coreexception.class.inc.php');
  70. require_once(APPROOT.'/core/dict.class.inc.php');
  71. require_once(APPROOT.'/core/attributedef.class.inc.php');
  72. require_once(APPROOT.'/core/filterdef.class.inc.php');
  73. require_once(APPROOT.'/core/stimulus.class.inc.php');
  74. require_once(APPROOT.'/core/MyHelpers.class.inc.php');
  75. require_once(APPROOT.'/core/oql/expression.class.inc.php');
  76. require_once(APPROOT.'/core/cmdbsource.class.inc.php');
  77. require_once(APPROOT.'/core/sqlquery.class.inc.php');
  78. require_once(APPROOT.'/core/sqlobjectquery.class.inc.php');
  79. require_once(APPROOT.'/core/sqlunionquery.class.inc.php');
  80. require_once(APPROOT.'/core/dbobject.class.php');
  81. require_once(APPROOT.'/core/dbsearch.class.php');
  82. require_once(APPROOT.'/core/dbobjectset.class.php');
  83. require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
  84. require_once(APPROOT.'/core/userrights.class.inc.php');
  85. require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
  86. $sConfigFile = $oConfig->GetLoadedFile();
  87. if (strlen($sConfigFile) > 0)
  88. {
  89. $this->log_info("MetaModel::Startup from $sConfigFile (ModelOnly = $bModelOnly)");
  90. }
  91. else
  92. {
  93. $this->log_info("MetaModel::Startup (ModelOnly = $bModelOnly)");
  94. }
  95. if (!$bUseCache)
  96. {
  97. // Reset the cache for the first use !
  98. MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv);
  99. }
  100. MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sTargetEnv);
  101. if ($this->oExtensionsMap === null)
  102. {
  103. $this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
  104. }
  105. }
  106. /**
  107. * Analyzes the current installation and the possibilities
  108. *
  109. * @param Config $oConfig Defines the target environment (DB)
  110. * @param mixed $modulesPath Either a single string or an array of absolute paths
  111. * @param bool $bAbortOnMissingDependency ...
  112. * @param hash $aModulesToLoad List of modules to search for, defaults to all if ommitted
  113. * @return hash Array with the following format:
  114. * array =>
  115. * 'iTop' => array(
  116. * 'version_db' => ... (could be empty in case of a fresh install)
  117. * 'version_code => ...
  118. * )
  119. * <module_name> => array(
  120. * 'version_db' => ...
  121. * 'version_code' => ...
  122. * 'install' => array(
  123. * 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
  124. * 'message' => ...
  125. * )
  126. * 'uninstall' => array(
  127. * 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
  128. * 'message' => ...
  129. * )
  130. * 'label' => ...
  131. * 'dependencies' => array(<module1>, <module2>, ...)
  132. * 'visible' => true | false
  133. * )
  134. * )
  135. */
  136. public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
  137. {
  138. $aRes = array(
  139. ROOT_MODULE => array(
  140. 'version_db' => '',
  141. 'name_db' => '',
  142. 'version_code' => ITOP_VERSION.'.'.ITOP_REVISION,
  143. 'name_code' => ITOP_APPLICATION,
  144. )
  145. );
  146. $aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath);
  147. $aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
  148. foreach($aModules as $sModuleId => $aModuleInfo)
  149. {
  150. list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
  151. if ($sModuleName == '')
  152. {
  153. throw new Exception("Missing name for the module: '$sModuleId'");
  154. }
  155. if ($sModuleVersion == '')
  156. {
  157. // The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not)
  158. //throw new Exception("Missing version for the module: '$sModuleId'");
  159. $sModuleVersion = '1.0.0';
  160. }
  161. $sModuleAppVersion = $aModuleInfo['itop_version'];
  162. $aModuleInfo['version_db'] = '';
  163. $aModuleInfo['version_code'] = $sModuleVersion;
  164. if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2')))
  165. {
  166. // This module is NOT compatible with the current version
  167. $aModuleInfo['install'] = array(
  168. 'flag' => MODULE_ACTION_IMPOSSIBLE,
  169. 'message' => 'the module is not compatible with the current version of the application'
  170. );
  171. }
  172. elseif ($aModuleInfo['mandatory'])
  173. {
  174. $aModuleInfo['install'] = array(
  175. 'flag' => MODULE_ACTION_MANDATORY,
  176. 'message' => 'the module is part of the application'
  177. );
  178. }
  179. else
  180. {
  181. $aModuleInfo['install'] = array(
  182. 'flag' => MODULE_ACTION_OPTIONAL,
  183. 'message' => ''
  184. );
  185. }
  186. $aRes[$sModuleName] = $aModuleInfo;
  187. }
  188. try
  189. {
  190. require_once(APPROOT.'/core/cmdbsource.class.inc.php');
  191. CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName());
  192. CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation());
  193. $aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->GetDBSubname()."priv_module_install");
  194. }
  195. catch (MySQLException $e)
  196. {
  197. // No database or erroneous information
  198. $aSelectInstall = array();
  199. }
  200. // Build the list of installed module (get the latest installation)
  201. //
  202. $aInstallByModule = array(); // array of <module> => array ('installed' => timestamp, 'version' => <version>)
  203. $iRootId = 0;
  204. foreach ($aSelectInstall as $aInstall)
  205. {
  206. if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel'))
  207. {
  208. // Root module, what is its ID ?
  209. $iId = (int) $aInstall['id'];
  210. if ($iId > $iRootId)
  211. {
  212. $iRootId = $iId;
  213. }
  214. }
  215. }
  216. foreach ($aSelectInstall as $aInstall)
  217. {
  218. //$aInstall['comment']; // unsused
  219. $iInstalled = strtotime($aInstall['installed']);
  220. $sModuleName = $aInstall['name'];
  221. $sModuleVersion = $aInstall['version'];
  222. if ($sModuleVersion == '')
  223. {
  224. // Though the version cannot be empty in iTop 2.0, it used to be possible
  225. // therefore we have to put something here or the module will not be considered
  226. // as being installed
  227. $sModuleVersion = '0.0.0';
  228. }
  229. if ($aInstall['parent_id'] == 0)
  230. {
  231. $sModuleName = ROOT_MODULE;
  232. }
  233. else if($aInstall['parent_id'] != $iRootId)
  234. {
  235. // Skip all modules belonging to previous installations
  236. continue;
  237. }
  238. if (array_key_exists($sModuleName, $aInstallByModule))
  239. {
  240. if ($iInstalled < $aInstallByModule[$sModuleName]['installed'])
  241. {
  242. continue;
  243. }
  244. }
  245. if ($aInstall['parent_id'] == 0)
  246. {
  247. $aRes[$sModuleName]['version_db'] = $sModuleVersion;
  248. $aRes[$sModuleName]['name_db'] = $aInstall['name'];
  249. }
  250. $aInstallByModule[$sModuleName]['installed'] = $iInstalled;
  251. $aInstallByModule[$sModuleName]['version'] = $sModuleVersion;
  252. }
  253. // Adjust the list of proposed modules
  254. //
  255. foreach ($aInstallByModule as $sModuleName => $aModuleDB)
  256. {
  257. if ($sModuleName == ROOT_MODULE) continue; // Skip the main module
  258. if (!array_key_exists($sModuleName, $aRes))
  259. {
  260. // A module was installed, it is not proposed in the new build... skip
  261. continue;
  262. }
  263. $aRes[$sModuleName]['version_db'] = $aModuleDB['version'];
  264. if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY)
  265. {
  266. $aRes[$sModuleName]['uninstall'] = array(
  267. 'flag' => MODULE_ACTION_IMPOSSIBLE,
  268. 'message' => 'the module is part of the application'
  269. );
  270. }
  271. else
  272. {
  273. $aRes[$sModuleName]['uninstall'] = array(
  274. 'flag' => MODULE_ACTION_OPTIONAL,
  275. 'message' => ''
  276. );
  277. }
  278. }
  279. return $aRes;
  280. }
  281. public function WriteConfigFileSafe($oConfig)
  282. {
  283. self::MakeDirSafe(APPCONF);
  284. self::MakeDirSafe(APPCONF.$this->sTargetEnv);
  285. $sTargetConfigFile = APPCONF.$this->sTargetEnv.'/'.ITOP_CONFIG_FILE;
  286. // Write the config file
  287. @chmod($sTargetConfigFile, 0770); // In case it exists: RWX for owner and group, nothing for others
  288. $oConfig->WriteToFile($sTargetConfigFile);
  289. @chmod($sTargetConfigFile, 0440); // Read-only for owner and group, nothing for others
  290. }
  291. /**
  292. * Get the installed modules (only the installed ones)
  293. */
  294. protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
  295. {
  296. $sSourceDirFull = APPROOT.$sSourceDir;
  297. if (!is_dir($sSourceDirFull))
  298. {
  299. throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
  300. }
  301. $aDirsToCompile = array($sSourceDirFull);
  302. if (is_dir(APPROOT.'extensions'))
  303. {
  304. $aDirsToCompile[] = APPROOT.'extensions';
  305. }
  306. $sExtraDir = APPROOT.'data/'.$this->sTargetEnv.'-modules/';
  307. if (is_dir($sExtraDir))
  308. {
  309. $aDirsToCompile[] = $sExtraDir;
  310. }
  311. $aRet = array();
  312. // Determine the installed modules and extensions
  313. //
  314. $oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
  315. $oSourceEnv = new RunTimeEnvironment($sSourceEnv);
  316. $aAvailableModules = $oSourceEnv->AnalyzeInstallation($oSourceConfig, $aDirsToCompile);
  317. // Actually read the modules available for the target environment,
  318. // but get the selection from the source environment and finally
  319. // mark as (automatically) chosen alll the "remote" modules present in the
  320. // target environment (data/<target-env>-modules)
  321. // The actual choices will be recorded by RecordInstallation below
  322. $this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
  323. $this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
  324. foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
  325. {
  326. if($oExtension->sSource == iTopExtension::SOURCE_REMOTE)
  327. {
  328. $this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
  329. }
  330. }
  331. // Do load the required modules
  332. //
  333. $oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
  334. $aRet[$oDictModule->GetName()] = $oDictModule;
  335. $oFactory = new ModelFactory($aDirsToCompile);
  336. $sDeltaFile = APPROOT.'core/datamodel.core.xml';
  337. if (file_exists($sDeltaFile))
  338. {
  339. $oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
  340. $aRet[$oCoreModule->GetName()] = $oCoreModule;
  341. }
  342. $sDeltaFile = APPROOT.'application/datamodel.application.xml';
  343. if (file_exists($sDeltaFile))
  344. {
  345. $oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
  346. $aRet[$oApplicationModule->GetName()] = $oApplicationModule;
  347. }
  348. $aModules = $oFactory->FindModules();
  349. foreach($aModules as $oModule)
  350. {
  351. $sModule = $oModule->GetName();
  352. $sModuleRootDir = $oModule->GetRootDir();
  353. $bIsExtra = (strpos($sModuleRootDir, $sExtraDir) !== false);
  354. if (array_key_exists($sModule, $aAvailableModules))
  355. {
  356. if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) //Extra modules are always unless they are 'AutoSelect'
  357. {
  358. $aRet[$oModule->GetName()] = $oModule;
  359. }
  360. }
  361. }
  362. // Now process the 'AutoSelect' modules
  363. do
  364. {
  365. // Loop while new modules are added...
  366. $bModuleAdded = false;
  367. foreach($aModules as $oModule)
  368. {
  369. if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect())
  370. {
  371. try
  372. {
  373. $bSelected = false;
  374. SetupInfo::SetSelectedModules($aRet);
  375. eval('$bSelected = ('.$oModule->GetAutoSelect().');');
  376. }
  377. catch(Exception $e)
  378. {
  379. $bSelected = false;
  380. }
  381. if ($bSelected)
  382. {
  383. $aRet[$oModule->GetName()] = $oModule; // store the Id of the selected module
  384. $bModuleAdded = true;
  385. }
  386. }
  387. }
  388. }
  389. while($bModuleAdded);
  390. $sDeltaFile = APPROOT.'data/'.$this->sTargetEnv.'.delta.xml';
  391. if (file_exists($sDeltaFile))
  392. {
  393. $oDelta = new MFDeltaModule($sDeltaFile);
  394. $aRet[$oDelta->GetName()] = $oDelta;
  395. }
  396. return $aRet;
  397. }
  398. /**
  399. * Compile the data model by imitating the given environment
  400. * The list of modules to be installed in the target environment is:
  401. * - the list of modules present in the "source_dir" (defined by the source environment) which are marked as "installed" in the source environment's database
  402. * - plus the list of modules present in the "extra" directory of the target environment: data/<target_environment>-modules/
  403. * @param string $sSourceEnv The name of the source environment to 'imitate'
  404. * @param bool $bUseSymLinks Whether to create symbolic links instead of copies
  405. */
  406. public function CompileFrom($sSourceEnv, $bUseSymLinks = false)
  407. {
  408. $oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv));
  409. $sSourceDir = $oSourceConfig->Get('source_dir');
  410. $sSourceDirFull = APPROOT.$sSourceDir;
  411. // Do load the required modules
  412. //
  413. $oFactory = new ModelFactory($sSourceDirFull);
  414. foreach($this->GetMFModulesToCompile($sSourceEnv, $sSourceDir) as $oModule)
  415. {
  416. if ($oModule instanceof MFDeltaModule)
  417. {
  418. // Just before loading the delta, let's save an image of the datamodel
  419. // in case there is no delta the operation will be done after the end of the loop
  420. $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml');
  421. }
  422. $oFactory->LoadModule($oModule);
  423. if ($oFactory->HasLoadErrors())
  424. {
  425. break;
  426. }
  427. }
  428. if ($oFactory->HasLoadErrors())
  429. {
  430. foreach($oFactory->GetLoadErrors() as $sModuleId => $aErrors)
  431. {
  432. echo "<h3>Module: ".$sModuleId."</h3>\n";
  433. foreach($aErrors as $oXmlError)
  434. {
  435. echo "<p>File: ".$oXmlError->file." Line:".$oXmlError->line." Message:".$oXmlError->message."</p>\n";
  436. }
  437. }
  438. }
  439. else
  440. {
  441. if ($oModule instanceof MFDeltaModule)
  442. {
  443. // A delta was loaded, let's save a second copy of the datamodel
  444. $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'-with-delta.xml');
  445. }
  446. else
  447. {
  448. // No delta was loaded, let's save the datamodel now
  449. $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml');
  450. }
  451. $sTargetDir = APPROOT.'env-'.$this->sTargetEnv;
  452. self::MakeDirSafe($sTargetDir);
  453. $oMFCompiler = new MFCompiler($oFactory);
  454. $oMFCompiler->Compile($sTargetDir, null, $bUseSymLinks);
  455. $sCacheDir = APPROOT.'data/cache-'.$this->sTargetEnv;
  456. SetupUtils::builddir($sCacheDir);
  457. SetupUtils::tidydir($sCacheDir);
  458. MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv);
  459. }
  460. }
  461. /**
  462. * Helper function to create the database structure
  463. * @return boolean true on success, false otherwise
  464. */
  465. public function CreateDatabaseStructure(Config $oConfig, $sMode)
  466. {
  467. if (strlen($oConfig->GetDBSubname()) > 0)
  468. {
  469. $this->log_info("Creating the structure in '".$oConfig->GetDBName()."' (table names prefixed by '".$oConfig->GetDBSubname()."').");
  470. }
  471. else
  472. {
  473. $this->log_info("Creating the structure in '".$oConfig->GetDBSubname()."'.");
  474. }
  475. //MetaModel::CheckDefinitions();
  476. if ($sMode == 'install')
  477. {
  478. if (!MetaModel::DBExists(/* bMustBeComplete */ false))
  479. {
  480. MetaModel::DBCreate(array($this, 'LogQueryCallback'));
  481. $this->log_ok("Database structure successfully created.");
  482. }
  483. else
  484. {
  485. if (strlen($oConfig->GetDBSubname()) > 0)
  486. {
  487. throw new Exception("Error: found iTop tables into the database '".$oConfig->GetDBName()."' (prefix: '".$oConfig->GetDBSubname()."'). Please, try selecting another database instance or specify another prefix to prevent conflicting table names.");
  488. }
  489. else
  490. {
  491. throw new Exception("Error: found iTop tables into the database '".$oConfig->GetDBName()."'. Please, try selecting another database instance or specify a prefix to prevent conflicting table names.");
  492. }
  493. }
  494. }
  495. else
  496. {
  497. if (MetaModel::DBExists(/* bMustBeComplete */ false))
  498. {
  499. // Have it work fine even if the DB has been set in read-only mode for the users
  500. // (fix copied from RunTimeEnvironment::RecordInstallation)
  501. $iPrevAccessMode = $oConfig->Get('access_mode');
  502. $oConfig->Set('access_mode', ACCESS_FULL);
  503. MetaModel::DBCreate(array($this, 'LogQueryCallback'));
  504. $this->log_ok("Database structure successfully updated.");
  505. // Check (and update only if it seems needed) the hierarchical keys
  506. ob_start();
  507. MetaModel::CheckHKeys(false /* bDiagnosticsOnly */, true /* bVerbose*/, true /* bForceUpdate */); // Since in 1.2-beta the detection was buggy, let's force the rebuilding of HKeys
  508. $sFeedback = ob_get_clean();
  509. $this->log_ok("Hierchical keys rebuilt: $sFeedback");
  510. // Check (and fix) data sync configuration
  511. ob_start();
  512. MetaModel::CheckDataSources(false /*$bDiagnostics*/, true/*$bVerbose*/);
  513. $sFeedback = ob_get_clean();
  514. $this->log_ok("Data sources checked: $sFeedback");
  515. // Fix meta enums
  516. ob_start();
  517. MetaModel::RebuildMetaEnums(true /*bVerbose*/);
  518. $sFeedback = ob_get_clean();
  519. $this->log_ok("Meta enums rebuilt: $sFeedback");
  520. // Restore the previous access mode
  521. $oConfig->Set('access_mode', $iPrevAccessMode);
  522. }
  523. else
  524. {
  525. if (strlen($oConfig->GetDBSubname()) > 0)
  526. {
  527. throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."' (prefix: '".$oConfig->GetDBSubname()."'). Please, try selecting another database instance.");
  528. }
  529. else
  530. {
  531. throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."'. Please, try selecting another database instance.");
  532. }
  533. }
  534. }
  535. return true;
  536. }
  537. public function UpdatePredefinedObjects()
  538. {
  539. // Have it work fine even if the DB has been set in read-only mode for the users
  540. $oConfig = MetaModel::GetConfig();
  541. $iPrevAccessMode = $oConfig->Get('access_mode');
  542. $oConfig->Set('access_mode', ACCESS_FULL);
  543. // Constant classes (e.g. User profiles)
  544. //
  545. foreach (MetaModel::GetClasses() as $sClass)
  546. {
  547. $aPredefinedObjects = call_user_func(array(
  548. $sClass,
  549. 'GetPredefinedObjects'
  550. ));
  551. if ($aPredefinedObjects != null)
  552. {
  553. $this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements.");
  554. // Create/Delete/Update objects of this class,
  555. // according to the given constant values
  556. //
  557. $aDBIds = array();
  558. $oAll = new DBObjectSet(new DBObjectSearch($sClass));
  559. while ($oObj = $oAll->Fetch())
  560. {
  561. if (array_key_exists($oObj->GetKey(), $aPredefinedObjects))
  562. {
  563. $aObjValues = $aPredefinedObjects[$oObj->GetKey()];
  564. foreach ($aObjValues as $sAttCode => $value)
  565. {
  566. $oObj->Set($sAttCode, $value);
  567. }
  568. $oObj->DBUpdate();
  569. $aDBIds[$oObj->GetKey()] = true;
  570. }
  571. else
  572. {
  573. $oObj->DBDelete();
  574. }
  575. }
  576. foreach ($aPredefinedObjects as $iRefId => $aObjValues)
  577. {
  578. if (! array_key_exists($iRefId, $aDBIds))
  579. {
  580. $oNewObj = MetaModel::NewObject($sClass);
  581. $oNewObj->SetKey($iRefId);
  582. foreach ($aObjValues as $sAttCode => $value)
  583. {
  584. $oNewObj->Set($sAttCode, $value);
  585. }
  586. $oNewObj->DBInsert();
  587. }
  588. }
  589. }
  590. }
  591. // Restore the previous access mode
  592. $oConfig->Set('access_mode', $iPrevAccessMode);
  593. }
  594. public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sModulesRelativePath, $sShortComment = null)
  595. {
  596. // Have it work fine even if the DB has been set in read-only mode for the users
  597. $iPrevAccessMode = $oConfig->Get('access_mode');
  598. $oConfig->Set('access_mode', ACCESS_FULL);
  599. if (CMDBSource::DBName() == '')
  600. {
  601. // In case this has not yet been done
  602. CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName());
  603. CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation());
  604. }
  605. if ($sShortComment === null)
  606. {
  607. $sShortComment = 'Done by the setup program';
  608. }
  609. $sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE;
  610. // Record datamodel version
  611. $aData = array(
  612. 'source_dir' => $oConfig->Get('source_dir'),
  613. );
  614. $iInstallationTime = time(); // Make sure that all modules record the same installation time
  615. $oInstallRec = new ModuleInstallation();
  616. $oInstallRec->Set('name', DATAMODEL_MODULE);
  617. $oInstallRec->Set('version', $sDataModelVersion);
  618. $oInstallRec->Set('comment', json_encode($aData));
  619. $oInstallRec->Set('parent_id', 0); // root module
  620. $oInstallRec->Set('installed', $iInstallationTime);
  621. $iMainItopRecord = $oInstallRec->DBInsertNoReload();
  622. // Record main installation
  623. $oInstallRec = new ModuleInstallation();
  624. $oInstallRec->Set('name', ITOP_APPLICATION);
  625. $oInstallRec->Set('version', ITOP_VERSION.'.'.ITOP_REVISION);
  626. $oInstallRec->Set('comment', $sMainComment);
  627. $oInstallRec->Set('parent_id', 0); // root module
  628. $oInstallRec->Set('installed', $iInstallationTime);
  629. $iMainItopRecord = $oInstallRec->DBInsertNoReload();
  630. // Record installed modules and extensions
  631. //
  632. $aAvailableExtensions = array();
  633. $aAvailableModules = $this->AnalyzeInstallation($oConfig, APPROOT.$sModulesRelativePath);
  634. foreach($aSelectedModuleCodes as $sModuleId)
  635. {
  636. $aModuleData = $aAvailableModules[$sModuleId];
  637. $sName = $sModuleId;
  638. $sVersion = $aModuleData['version_code'];
  639. $aComments = array();
  640. $aComments[] = $sShortComment;
  641. if ($aModuleData['mandatory'])
  642. {
  643. $aComments[] = 'Mandatory';
  644. }
  645. else
  646. {
  647. $aComments[] = 'Optional';
  648. }
  649. if ($aModuleData['visible'])
  650. {
  651. $aComments[] = 'Visible (during the setup)';
  652. }
  653. else
  654. {
  655. $aComments[] = 'Hidden (selected automatically)';
  656. }
  657. foreach ($aModuleData['dependencies'] as $sDependOn)
  658. {
  659. $aComments[] = "Depends on module: $sDependOn";
  660. }
  661. $sComment = implode("\n", $aComments);
  662. $oInstallRec = new ModuleInstallation();
  663. $oInstallRec->Set('name', $sName);
  664. $oInstallRec->Set('version', $sVersion);
  665. $oInstallRec->Set('comment', $sComment);
  666. $oInstallRec->Set('parent_id', $iMainItopRecord);
  667. $oInstallRec->Set('installed', $iInstallationTime);
  668. $oInstallRec->DBInsertNoReload();
  669. }
  670. if ($this->oExtensionsMap)
  671. {
  672. // Mark as chosen the selected extensions code passed to us
  673. // Note: some other extensions may already be marked as chosen
  674. foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
  675. {
  676. if (in_array($oExtension->sCode, $aSelectedExtensionCodes))
  677. {
  678. $this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
  679. }
  680. }
  681. foreach($this->oExtensionsMap->GetChoices() as $oExtension)
  682. {
  683. $oInstallRec = new ExtensionInstallation();
  684. $oInstallRec->Set('code', $oExtension->sCode);
  685. $oInstallRec->Set('label', $oExtension->sLabel);
  686. $oInstallRec->Set('version', $oExtension->sVersion);
  687. $oInstallRec->Set('source', $oExtension->sSource);
  688. $oInstallRec->Set('installed', $iInstallationTime);
  689. $oInstallRec->DBInsertNoReload();
  690. }
  691. }
  692. // Restore the previous access mode
  693. $oConfig->Set('access_mode', $iPrevAccessMode);
  694. // Database is created, installation has been tracked into it
  695. return true;
  696. }
  697. public function GetApplicationVersion(Config $oConfig)
  698. {
  699. $aResult = false;
  700. try
  701. {
  702. require_once(APPROOT.'/core/cmdbsource.class.inc.php');
  703. CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName());
  704. CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation());
  705. $sSQLQuery = "SELECT * FROM ".$oConfig->GetDBSubname()."priv_module_install";
  706. $aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
  707. }
  708. catch (MySQLException $e)
  709. {
  710. // No database or erroneous information
  711. $this->log_error('Can not connect to the database: host: '.$oConfig->GetDBHost().', user:'.$oConfig->GetDBUser().', pwd:'.$oConfig->GetDBPwd().', db name:'.$oConfig->GetDBName());
  712. $this->log_error('Exception '.$e->getMessage());
  713. return false;
  714. }
  715. // Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
  716. foreach ($aSelectInstall as $aInstall)
  717. {
  718. $sModuleVersion = $aInstall['version'];
  719. if ($sModuleVersion == '')
  720. {
  721. // Though the version cannot be empty in iTop 2.0, it used to be possible
  722. // therefore we have to put something here or the module will not be considered
  723. // as being installed
  724. $sModuleVersion = '0.0.0';
  725. }
  726. if ($aInstall['parent_id'] == 0)
  727. {
  728. if ($aInstall['name'] == DATAMODEL_MODULE)
  729. {
  730. $aResult['datamodel_version'] = $sModuleVersion;
  731. $aComments = json_decode($aInstall['comment'], true);
  732. if (is_array($aComments))
  733. {
  734. $aResult = array_merge($aResult, $aComments);
  735. }
  736. }
  737. else
  738. {
  739. $aResult['product_name'] = $aInstall['name'];
  740. $aResult['product_version'] = $sModuleVersion;
  741. }
  742. }
  743. }
  744. if (!array_key_exists('datamodel_version', $aResult))
  745. {
  746. // Versions prior to 2.0 did not record the version of the datamodel
  747. // so assume that the datamodel version is equal to the application version
  748. $aResult['datamodel_version'] = $aResult['product_version'];
  749. }
  750. $this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']);
  751. return $aResult;
  752. }
  753. public static function MakeDirSafe($sDir)
  754. {
  755. if (!is_dir($sDir))
  756. {
  757. if (!@mkdir($sDir))
  758. {
  759. throw new Exception("Failed to create directory '$sDir', please check that the web server process has enough rights to create the directory.");
  760. }
  761. @chmod($sDir, 0770); // RWX for owner and group, nothing for others
  762. }
  763. }
  764. /**
  765. * Wrappers for logging into the setup log files
  766. */
  767. protected function log_error($sText)
  768. {
  769. SetupPage::log_error($sText);
  770. }
  771. protected function log_warning($sText)
  772. {
  773. SetupPage::log_warning($sText);
  774. }
  775. protected function log_info($sText)
  776. {
  777. SetupPage::log_info($sText);
  778. }
  779. protected function log_ok($sText)
  780. {
  781. SetupPage::log_ok($sText);
  782. }
  783. public function GetCurrentDataModelVersion()
  784. {
  785. $oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'");
  786. $oSet = new DBObjectSet($oSearch, array(), array('installed' => false));
  787. $oLatestDM = $oSet->Fetch();
  788. if ($oLatestDM == null)
  789. {
  790. return '0.0.0';
  791. }
  792. return $oLatestDM->Get('version');
  793. }
  794. } // End of class