runtimeenv.class.inc.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  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. require_once(APPROOT.'/core/dict.class.inc.php');
  459. MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv);
  460. }
  461. }
  462. /**
  463. * Helper function to create the database structure
  464. * @return boolean true on success, false otherwise
  465. */
  466. public function CreateDatabaseStructure(Config $oConfig, $sMode)
  467. {
  468. if (strlen($oConfig->GetDBSubname()) > 0)
  469. {
  470. $this->log_info("Creating the structure in '".$oConfig->GetDBName()."' (table names prefixed by '".$oConfig->GetDBSubname()."').");
  471. }
  472. else
  473. {
  474. $this->log_info("Creating the structure in '".$oConfig->GetDBSubname()."'.");
  475. }
  476. //MetaModel::CheckDefinitions();
  477. if ($sMode == 'install')
  478. {
  479. if (!MetaModel::DBExists(/* bMustBeComplete */ false))
  480. {
  481. MetaModel::DBCreate(array($this, 'LogQueryCallback'));
  482. $this->log_ok("Database structure successfully created.");
  483. }
  484. else
  485. {
  486. if (strlen($oConfig->GetDBSubname()) > 0)
  487. {
  488. 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.");
  489. }
  490. else
  491. {
  492. 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.");
  493. }
  494. }
  495. }
  496. else
  497. {
  498. if (MetaModel::DBExists(/* bMustBeComplete */ false))
  499. {
  500. // Have it work fine even if the DB has been set in read-only mode for the users
  501. // (fix copied from RunTimeEnvironment::RecordInstallation)
  502. $iPrevAccessMode = $oConfig->Get('access_mode');
  503. $oConfig->Set('access_mode', ACCESS_FULL);
  504. MetaModel::DBCreate(array($this, 'LogQueryCallback'));
  505. $this->log_ok("Database structure successfully updated.");
  506. // Check (and update only if it seems needed) the hierarchical keys
  507. ob_start();
  508. MetaModel::CheckHKeys(false /* bDiagnosticsOnly */, true /* bVerbose*/, true /* bForceUpdate */); // Since in 1.2-beta the detection was buggy, let's force the rebuilding of HKeys
  509. $sFeedback = ob_get_clean();
  510. $this->log_ok("Hierchical keys rebuilt: $sFeedback");
  511. // Check (and fix) data sync configuration
  512. ob_start();
  513. MetaModel::CheckDataSources(false /*$bDiagnostics*/, true/*$bVerbose*/);
  514. $sFeedback = ob_get_clean();
  515. $this->log_ok("Data sources checked: $sFeedback");
  516. // Fix meta enums
  517. ob_start();
  518. MetaModel::RebuildMetaEnums(true /*bVerbose*/);
  519. $sFeedback = ob_get_clean();
  520. $this->log_ok("Meta enums rebuilt: $sFeedback");
  521. // Restore the previous access mode
  522. $oConfig->Set('access_mode', $iPrevAccessMode);
  523. }
  524. else
  525. {
  526. if (strlen($oConfig->GetDBSubname()) > 0)
  527. {
  528. throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."' (prefix: '".$oConfig->GetDBSubname()."'). Please, try selecting another database instance.");
  529. }
  530. else
  531. {
  532. throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."'. Please, try selecting another database instance.");
  533. }
  534. }
  535. }
  536. return true;
  537. }
  538. public function UpdatePredefinedObjects()
  539. {
  540. // Have it work fine even if the DB has been set in read-only mode for the users
  541. $oConfig = MetaModel::GetConfig();
  542. $iPrevAccessMode = $oConfig->Get('access_mode');
  543. $oConfig->Set('access_mode', ACCESS_FULL);
  544. // Constant classes (e.g. User profiles)
  545. //
  546. foreach (MetaModel::GetClasses() as $sClass)
  547. {
  548. $aPredefinedObjects = call_user_func(array(
  549. $sClass,
  550. 'GetPredefinedObjects'
  551. ));
  552. if ($aPredefinedObjects != null)
  553. {
  554. $this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements.");
  555. // Create/Delete/Update objects of this class,
  556. // according to the given constant values
  557. //
  558. $aDBIds = array();
  559. $oAll = new DBObjectSet(new DBObjectSearch($sClass));
  560. while ($oObj = $oAll->Fetch())
  561. {
  562. if (array_key_exists($oObj->GetKey(), $aPredefinedObjects))
  563. {
  564. $aObjValues = $aPredefinedObjects[$oObj->GetKey()];
  565. foreach ($aObjValues as $sAttCode => $value)
  566. {
  567. $oObj->Set($sAttCode, $value);
  568. }
  569. $oObj->DBUpdate();
  570. $aDBIds[$oObj->GetKey()] = true;
  571. }
  572. else
  573. {
  574. $oObj->DBDelete();
  575. }
  576. }
  577. foreach ($aPredefinedObjects as $iRefId => $aObjValues)
  578. {
  579. if (! array_key_exists($iRefId, $aDBIds))
  580. {
  581. $oNewObj = MetaModel::NewObject($sClass);
  582. $oNewObj->SetKey($iRefId);
  583. foreach ($aObjValues as $sAttCode => $value)
  584. {
  585. $oNewObj->Set($sAttCode, $value);
  586. }
  587. $oNewObj->DBInsert();
  588. }
  589. }
  590. }
  591. }
  592. // Restore the previous access mode
  593. $oConfig->Set('access_mode', $iPrevAccessMode);
  594. }
  595. public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sModulesRelativePath, $sShortComment = null)
  596. {
  597. // Have it work fine even if the DB has been set in read-only mode for the users
  598. $iPrevAccessMode = $oConfig->Get('access_mode');
  599. $oConfig->Set('access_mode', ACCESS_FULL);
  600. if (CMDBSource::DBName() == '')
  601. {
  602. // In case this has not yet been done
  603. CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName());
  604. CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation());
  605. }
  606. if ($sShortComment === null)
  607. {
  608. $sShortComment = 'Done by the setup program';
  609. }
  610. $sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE;
  611. // Record datamodel version
  612. $aData = array(
  613. 'source_dir' => $oConfig->Get('source_dir'),
  614. );
  615. $iInstallationTime = time(); // Make sure that all modules record the same installation time
  616. $oInstallRec = new ModuleInstallation();
  617. $oInstallRec->Set('name', DATAMODEL_MODULE);
  618. $oInstallRec->Set('version', $sDataModelVersion);
  619. $oInstallRec->Set('comment', json_encode($aData));
  620. $oInstallRec->Set('parent_id', 0); // root module
  621. $oInstallRec->Set('installed', $iInstallationTime);
  622. $iMainItopRecord = $oInstallRec->DBInsertNoReload();
  623. // Record main installation
  624. $oInstallRec = new ModuleInstallation();
  625. $oInstallRec->Set('name', ITOP_APPLICATION);
  626. $oInstallRec->Set('version', ITOP_VERSION.'.'.ITOP_REVISION);
  627. $oInstallRec->Set('comment', $sMainComment);
  628. $oInstallRec->Set('parent_id', 0); // root module
  629. $oInstallRec->Set('installed', $iInstallationTime);
  630. $iMainItopRecord = $oInstallRec->DBInsertNoReload();
  631. // Record installed modules and extensions
  632. //
  633. $aAvailableExtensions = array();
  634. $aAvailableModules = $this->AnalyzeInstallation($oConfig, APPROOT.$sModulesRelativePath);
  635. foreach($aSelectedModuleCodes as $sModuleId)
  636. {
  637. $aModuleData = $aAvailableModules[$sModuleId];
  638. $sName = $sModuleId;
  639. $sVersion = $aModuleData['version_code'];
  640. $aComments = array();
  641. $aComments[] = $sShortComment;
  642. if ($aModuleData['mandatory'])
  643. {
  644. $aComments[] = 'Mandatory';
  645. }
  646. else
  647. {
  648. $aComments[] = 'Optional';
  649. }
  650. if ($aModuleData['visible'])
  651. {
  652. $aComments[] = 'Visible (during the setup)';
  653. }
  654. else
  655. {
  656. $aComments[] = 'Hidden (selected automatically)';
  657. }
  658. foreach ($aModuleData['dependencies'] as $sDependOn)
  659. {
  660. $aComments[] = "Depends on module: $sDependOn";
  661. }
  662. $sComment = implode("\n", $aComments);
  663. $oInstallRec = new ModuleInstallation();
  664. $oInstallRec->Set('name', $sName);
  665. $oInstallRec->Set('version', $sVersion);
  666. $oInstallRec->Set('comment', $sComment);
  667. $oInstallRec->Set('parent_id', $iMainItopRecord);
  668. $oInstallRec->Set('installed', $iInstallationTime);
  669. $oInstallRec->DBInsertNoReload();
  670. }
  671. if ($this->oExtensionsMap)
  672. {
  673. // Mark as chosen the selected extensions code passed to us
  674. // Note: some other extensions may already be marked as chosen
  675. foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
  676. {
  677. if (in_array($oExtension->sCode, $aSelectedExtensionCodes))
  678. {
  679. $this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
  680. }
  681. }
  682. foreach($this->oExtensionsMap->GetChoices() as $oExtension)
  683. {
  684. $oInstallRec = new ExtensionInstallation();
  685. $oInstallRec->Set('code', $oExtension->sCode);
  686. $oInstallRec->Set('label', $oExtension->sLabel);
  687. $oInstallRec->Set('version', $oExtension->sVersion);
  688. $oInstallRec->Set('source', $oExtension->sSource);
  689. $oInstallRec->Set('installed', $iInstallationTime);
  690. $oInstallRec->DBInsertNoReload();
  691. }
  692. }
  693. // Restore the previous access mode
  694. $oConfig->Set('access_mode', $iPrevAccessMode);
  695. // Database is created, installation has been tracked into it
  696. return true;
  697. }
  698. public function GetApplicationVersion(Config $oConfig)
  699. {
  700. $aResult = false;
  701. try
  702. {
  703. require_once(APPROOT.'/core/cmdbsource.class.inc.php');
  704. CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName());
  705. CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation());
  706. $sSQLQuery = "SELECT * FROM ".$oConfig->GetDBSubname()."priv_module_install";
  707. $aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
  708. }
  709. catch (MySQLException $e)
  710. {
  711. // No database or erroneous information
  712. $this->log_error('Can not connect to the database: host: '.$oConfig->GetDBHost().', user:'.$oConfig->GetDBUser().', pwd:'.$oConfig->GetDBPwd().', db name:'.$oConfig->GetDBName());
  713. $this->log_error('Exception '.$e->getMessage());
  714. return false;
  715. }
  716. // Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
  717. foreach ($aSelectInstall as $aInstall)
  718. {
  719. $sModuleVersion = $aInstall['version'];
  720. if ($sModuleVersion == '')
  721. {
  722. // Though the version cannot be empty in iTop 2.0, it used to be possible
  723. // therefore we have to put something here or the module will not be considered
  724. // as being installed
  725. $sModuleVersion = '0.0.0';
  726. }
  727. if ($aInstall['parent_id'] == 0)
  728. {
  729. if ($aInstall['name'] == DATAMODEL_MODULE)
  730. {
  731. $aResult['datamodel_version'] = $sModuleVersion;
  732. $aComments = json_decode($aInstall['comment'], true);
  733. if (is_array($aComments))
  734. {
  735. $aResult = array_merge($aResult, $aComments);
  736. }
  737. }
  738. else
  739. {
  740. $aResult['product_name'] = $aInstall['name'];
  741. $aResult['product_version'] = $sModuleVersion;
  742. }
  743. }
  744. }
  745. if (!array_key_exists('datamodel_version', $aResult))
  746. {
  747. // Versions prior to 2.0 did not record the version of the datamodel
  748. // so assume that the datamodel version is equal to the application version
  749. $aResult['datamodel_version'] = $aResult['product_version'];
  750. }
  751. $this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']);
  752. return $aResult;
  753. }
  754. public static function MakeDirSafe($sDir)
  755. {
  756. if (!is_dir($sDir))
  757. {
  758. if (!@mkdir($sDir))
  759. {
  760. throw new Exception("Failed to create directory '$sDir', please check that the web server process has enough rights to create the directory.");
  761. }
  762. @chmod($sDir, 0770); // RWX for owner and group, nothing for others
  763. }
  764. }
  765. /**
  766. * Wrappers for logging into the setup log files
  767. */
  768. protected function log_error($sText)
  769. {
  770. SetupPage::log_error($sText);
  771. }
  772. protected function log_warning($sText)
  773. {
  774. SetupPage::log_warning($sText);
  775. }
  776. protected function log_info($sText)
  777. {
  778. SetupPage::log_info($sText);
  779. }
  780. protected function log_ok($sText)
  781. {
  782. SetupPage::log_ok($sText);
  783. }
  784. public function GetCurrentDataModelVersion()
  785. {
  786. $oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'");
  787. $oSet = new DBObjectSet($oSearch, array(), array('installed' => false));
  788. $oLatestDM = $oSet->Fetch();
  789. if ($oLatestDM == null)
  790. {
  791. return '0.0.0';
  792. }
  793. return $oLatestDM->Get('version');
  794. }
  795. } // End of class