/** * Manage a runtime environment * * @copyright Copyright (C) 2010-2017 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ require_once(APPROOT."setup/modulediscovery.class.inc.php"); require_once(APPROOT.'setup/modelfactory.class.inc.php'); require_once(APPROOT.'setup/compiler.class.inc.php'); require_once(APPROOT.'setup/extensionsmap.class.inc.php'); require_once(APPROOT.'core/metamodel.class.php'); define ('MODULE_ACTION_OPTIONAL', 1); define ('MODULE_ACTION_MANDATORY', 2); define ('MODULE_ACTION_IMPOSSIBLE', 3); define ('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application define ('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel class RunTimeEnvironment { /** * The name of the environment that the caller wants to build * @var string sFinalEnv */ protected $sFinalEnv; /** * Environment into which the build will be performed * @var string sTargetEnv */ protected $sTargetEnv; /** * Extensions map of the source environment * @var iTopExtensionsMap */ protected $oExtensionsMap; /** * Toolset for building a run-time environment * * @param string $sEnvironment (e.g. 'test') * @param bool $bAutoCommit (make the target environment directly, or build a temporary one) */ public function __construct($sEnvironment = 'production', $bAutoCommit = true) { $this->sFinalEnv = $sEnvironment; if ($bAutoCommit) { // Build directly onto the requested environment $this->sTargetEnv = $sEnvironment; } else { // Build into a temporary target $this->sTargetEnv = $sEnvironment.'-build'; } $this->oExtensionsMap = null; } /** * Return the full path to the compiled code (do not use after commit) * @return string */ public function GetBuildDir() { return APPROOT.'env-'.$this->sTargetEnv; } /** * Callback function for logging the queries run by the setup. * According to the documentation the function must be defined before passing it to call_user_func... * @param string $sQuery * @param float $fDuration * @return void */ public function LogQueryCallback($sQuery, $fDuration) { $this->log_info(sprintf('%.3fs - query: %s ', $fDuration, $sQuery)); } /** * Helper function to initialize the ORM and load the data model * from the given file * @param $oConfig object The configuration (volatile, not necessarily already on disk) * @param $bModelOnly boolean Whether or not to allow loading a data model with no corresponding DB * @return none */ public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false) { require_once(APPROOT.'/core/log.class.inc.php'); require_once(APPROOT.'/core/kpi.class.inc.php'); require_once(APPROOT.'/core/coreexception.class.inc.php'); require_once(APPROOT.'/core/dict.class.inc.php'); require_once(APPROOT.'/core/attributedef.class.inc.php'); require_once(APPROOT.'/core/filterdef.class.inc.php'); require_once(APPROOT.'/core/stimulus.class.inc.php'); require_once(APPROOT.'/core/MyHelpers.class.inc.php'); require_once(APPROOT.'/core/oql/expression.class.inc.php'); require_once(APPROOT.'/core/cmdbsource.class.inc.php'); require_once(APPROOT.'/core/sqlquery.class.inc.php'); require_once(APPROOT.'/core/sqlobjectquery.class.inc.php'); require_once(APPROOT.'/core/sqlunionquery.class.inc.php'); require_once(APPROOT.'/core/dbobject.class.php'); require_once(APPROOT.'/core/dbsearch.class.php'); require_once(APPROOT.'/core/dbobjectset.class.php'); require_once(APPROOT.'/application/cmdbabstract.class.inc.php'); require_once(APPROOT.'/core/userrights.class.inc.php'); require_once(APPROOT.'/setup/moduleinstallation.class.inc.php'); $sConfigFile = $oConfig->GetLoadedFile(); if (strlen($sConfigFile) > 0) { $this->log_info("MetaModel::Startup from $sConfigFile (ModelOnly = $bModelOnly)"); } else { $this->log_info("MetaModel::Startup (ModelOnly = $bModelOnly)"); } if (!$bUseCache) { // Reset the cache for the first use ! MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv); } MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sTargetEnv); if ($this->oExtensionsMap === null) { $this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv); } } /** * Analyzes the current installation and the possibilities * * @param Config $oConfig Defines the target environment (DB) * @param mixed $modulesPath Either a single string or an array of absolute paths * @param bool $bAbortOnMissingDependency ... * @param hash $aModulesToLoad List of modules to search for, defaults to all if ommitted * @return hash Array with the following format: * array => * 'iTop' => array( * 'version_db' => ... (could be empty in case of a fresh install) * 'version_code => ... * ) * => array( * 'version_db' => ... * 'version_code' => ... * 'install' => array( * 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY * 'message' => ... * ) * 'uninstall' => array( * 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY * 'message' => ... * ) * 'label' => ... * 'dependencies' => array(, , ...) * 'visible' => true | false * ) * ) */ public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency = false, $aModulesToLoad = null) { $aRes = array( ROOT_MODULE => array( 'version_db' => '', 'name_db' => '', 'version_code' => ITOP_VERSION.'.'.ITOP_REVISION, 'name_code' => ITOP_APPLICATION, ) ); $aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath); $aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad); foreach($aModules as $sModuleId => $aModuleInfo) { list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId); if ($sModuleName == '') { throw new Exception("Missing name for the module: '$sModuleId'"); } if ($sModuleVersion == '') { // The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not) //throw new Exception("Missing version for the module: '$sModuleId'"); $sModuleVersion = '1.0.0'; } $sModuleAppVersion = $aModuleInfo['itop_version']; $aModuleInfo['version_db'] = ''; $aModuleInfo['version_code'] = $sModuleVersion; if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2'))) { // This module is NOT compatible with the current version $aModuleInfo['install'] = array( 'flag' => MODULE_ACTION_IMPOSSIBLE, 'message' => 'the module is not compatible with the current version of the application' ); } elseif ($aModuleInfo['mandatory']) { $aModuleInfo['install'] = array( 'flag' => MODULE_ACTION_MANDATORY, 'message' => 'the module is part of the application' ); } else { $aModuleInfo['install'] = array( 'flag' => MODULE_ACTION_OPTIONAL, 'message' => '' ); } $aRes[$sModuleName] = $aModuleInfo; } try { require_once(APPROOT.'/core/cmdbsource.class.inc.php'); CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName()); CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation()); $aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->GetDBSubname()."priv_module_install"); } catch (MySQLException $e) { // No database or erroneous information $aSelectInstall = array(); } // Build the list of installed module (get the latest installation) // $aInstallByModule = array(); // array of => array ('installed' => timestamp, 'version' => ) $iRootId = 0; foreach ($aSelectInstall as $aInstall) { if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel')) { // Root module, what is its ID ? $iId = (int) $aInstall['id']; if ($iId > $iRootId) { $iRootId = $iId; } } } foreach ($aSelectInstall as $aInstall) { //$aInstall['comment']; // unsused $iInstalled = strtotime($aInstall['installed']); $sModuleName = $aInstall['name']; $sModuleVersion = $aInstall['version']; if ($sModuleVersion == '') { // Though the version cannot be empty in iTop 2.0, it used to be possible // therefore we have to put something here or the module will not be considered // as being installed $sModuleVersion = '0.0.0'; } if ($aInstall['parent_id'] == 0) { $sModuleName = ROOT_MODULE; } else if($aInstall['parent_id'] != $iRootId) { // Skip all modules belonging to previous installations continue; } if (array_key_exists($sModuleName, $aInstallByModule)) { if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) { continue; } } if ($aInstall['parent_id'] == 0) { $aRes[$sModuleName]['version_db'] = $sModuleVersion; $aRes[$sModuleName]['name_db'] = $aInstall['name']; } $aInstallByModule[$sModuleName]['installed'] = $iInstalled; $aInstallByModule[$sModuleName]['version'] = $sModuleVersion; } // Adjust the list of proposed modules // foreach ($aInstallByModule as $sModuleName => $aModuleDB) { if ($sModuleName == ROOT_MODULE) continue; // Skip the main module if (!array_key_exists($sModuleName, $aRes)) { // A module was installed, it is not proposed in the new build... skip continue; } $aRes[$sModuleName]['version_db'] = $aModuleDB['version']; if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY) { $aRes[$sModuleName]['uninstall'] = array( 'flag' => MODULE_ACTION_IMPOSSIBLE, 'message' => 'the module is part of the application' ); } else { $aRes[$sModuleName]['uninstall'] = array( 'flag' => MODULE_ACTION_OPTIONAL, 'message' => '' ); } } return $aRes; } public function WriteConfigFileSafe($oConfig) { self::MakeDirSafe(APPCONF); self::MakeDirSafe(APPCONF.$this->sTargetEnv); $sTargetConfigFile = APPCONF.$this->sTargetEnv.'/'.ITOP_CONFIG_FILE; // Write the config file @chmod($sTargetConfigFile, 0770); // In case it exists: RWX for owner and group, nothing for others $oConfig->WriteToFile($sTargetConfigFile); @chmod($sTargetConfigFile, 0440); // Read-only for owner and group, nothing for others } /** * Get the installed modules (only the installed ones) */ protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) { $sSourceDirFull = APPROOT.$sSourceDir; if (!is_dir($sSourceDirFull)) { throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)"); } $aDirsToCompile = array($sSourceDirFull); if (is_dir(APPROOT.'extensions')) { $aDirsToCompile[] = APPROOT.'extensions'; } $sExtraDir = APPROOT.'data/'.$this->sTargetEnv.'-modules/'; if (is_dir($sExtraDir)) { $aDirsToCompile[] = $sExtraDir; } $aRet = array(); // Determine the installed modules and extensions // $oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE); $oSourceEnv = new RunTimeEnvironment($sSourceEnv); $aAvailableModules = $oSourceEnv->AnalyzeInstallation($oSourceConfig, $aDirsToCompile); // Actually read the modules available for the target environment, // but get the selection from the source environment and finally // mark as (automatically) chosen alll the "remote" modules present in the // target environment (data/-modules) // The actual choices will be recorded by RecordInstallation below $this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv); $this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig); foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension) { if($oExtension->sSource == iTopExtension::SOURCE_REMOTE) { $this->oExtensionsMap->MarkAsChosen($oExtension->sCode); } } // Do load the required modules // $oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries'); $aRet[$oDictModule->GetName()] = $oDictModule; $oFactory = new ModelFactory($aDirsToCompile); $sDeltaFile = APPROOT.'core/datamodel.core.xml'; if (file_exists($sDeltaFile)) { $oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile); $aRet[$oCoreModule->GetName()] = $oCoreModule; } $sDeltaFile = APPROOT.'application/datamodel.application.xml'; if (file_exists($sDeltaFile)) { $oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile); $aRet[$oApplicationModule->GetName()] = $oApplicationModule; } $aModules = $oFactory->FindModules(); foreach($aModules as $oModule) { $sModule = $oModule->GetName(); $sModuleRootDir = $oModule->GetRootDir(); $bIsExtra = (strpos($sModuleRootDir, $sExtraDir) !== false); if (array_key_exists($sModule, $aAvailableModules)) { if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) //Extra modules are always unless they are 'AutoSelect' { $aRet[$oModule->GetName()] = $oModule; } } } // Now process the 'AutoSelect' modules do { // Loop while new modules are added... $bModuleAdded = false; foreach($aModules as $oModule) { if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect()) { try { $bSelected = false; SetupInfo::SetSelectedModules($aRet); eval('$bSelected = ('.$oModule->GetAutoSelect().');'); } catch(Exception $e) { $bSelected = false; } if ($bSelected) { $aRet[$oModule->GetName()] = $oModule; // store the Id of the selected module $bModuleAdded = true; } } } } while($bModuleAdded); $sDeltaFile = APPROOT.'data/'.$this->sTargetEnv.'.delta.xml'; if (file_exists($sDeltaFile)) { $oDelta = new MFDeltaModule($sDeltaFile); $aRet[$oDelta->GetName()] = $oDelta; } return $aRet; } /** * Compile the data model by imitating the given environment * The list of modules to be installed in the target environment is: * - 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 * - plus the list of modules present in the "extra" directory of the target environment: data/-modules/ * @param string $sSourceEnv The name of the source environment to 'imitate' * @param bool $bUseSymLinks Whether to create symbolic links instead of copies */ public function CompileFrom($sSourceEnv, $bUseSymLinks = false) { $oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv)); $sSourceDir = $oSourceConfig->Get('source_dir'); $sSourceDirFull = APPROOT.$sSourceDir; // Do load the required modules // $oFactory = new ModelFactory($sSourceDirFull); foreach($this->GetMFModulesToCompile($sSourceEnv, $sSourceDir) as $oModule) { if ($oModule instanceof MFDeltaModule) { // Just before loading the delta, let's save an image of the datamodel // in case there is no delta the operation will be done after the end of the loop $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml'); } $oFactory->LoadModule($oModule); if ($oFactory->HasLoadErrors()) { break; } } if ($oFactory->HasLoadErrors()) { foreach($oFactory->GetLoadErrors() as $sModuleId => $aErrors) { echo "

Module: ".$sModuleId."

\n"; foreach($aErrors as $oXmlError) { echo "

File: ".$oXmlError->file." Line:".$oXmlError->line." Message:".$oXmlError->message."

\n"; } } } else { if ($oModule instanceof MFDeltaModule) { // A delta was loaded, let's save a second copy of the datamodel $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'-with-delta.xml'); } else { // No delta was loaded, let's save the datamodel now $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml'); } $sTargetDir = APPROOT.'env-'.$this->sTargetEnv; self::MakeDirSafe($sTargetDir); $bSkipTempDir = ($this->sFinalEnv != $this->sTargetEnv); // No need for a temporary directory if sTargetEnv is already a temporary directory $oMFCompiler = new MFCompiler($oFactory); $oMFCompiler->Compile($sTargetDir, null, $bUseSymLinks, $bSkipTempDir); $sCacheDir = APPROOT.'data/cache-'.$this->sTargetEnv; SetupUtils::builddir($sCacheDir); SetupUtils::tidydir($sCacheDir); MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv); } } /** * Helper function to create the database structure * @return boolean true on success, false otherwise */ public function CreateDatabaseStructure(Config $oConfig, $sMode) { if (strlen($oConfig->GetDBSubname()) > 0) { $this->log_info("Creating the structure in '".$oConfig->GetDBName()."' (table names prefixed by '".$oConfig->GetDBSubname()."')."); } else { $this->log_info("Creating the structure in '".$oConfig->GetDBSubname()."'."); } //MetaModel::CheckDefinitions(); if ($sMode == 'install') { if (!MetaModel::DBExists(/* bMustBeComplete */ false)) { MetaModel::DBCreate(array($this, 'LogQueryCallback')); $this->log_ok("Database structure successfully created."); } else { if (strlen($oConfig->GetDBSubname()) > 0) { 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."); } else { 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."); } } } else { if (MetaModel::DBExists(/* bMustBeComplete */ false)) { // Have it work fine even if the DB has been set in read-only mode for the users // (fix copied from RunTimeEnvironment::RecordInstallation) $iPrevAccessMode = $oConfig->Get('access_mode'); $oConfig->Set('access_mode', ACCESS_FULL); MetaModel::DBCreate(array($this, 'LogQueryCallback')); $this->log_ok("Database structure successfully updated."); // Check (and update only if it seems needed) the hierarchical keys ob_start(); MetaModel::CheckHKeys(false /* bDiagnosticsOnly */, true /* bVerbose*/, true /* bForceUpdate */); // Since in 1.2-beta the detection was buggy, let's force the rebuilding of HKeys $sFeedback = ob_get_clean(); $this->log_ok("Hierchical keys rebuilt: $sFeedback"); // Check (and fix) data sync configuration ob_start(); MetaModel::CheckDataSources(false /*$bDiagnostics*/, true/*$bVerbose*/); $sFeedback = ob_get_clean(); $this->log_ok("Data sources checked: $sFeedback"); // Fix meta enums ob_start(); MetaModel::RebuildMetaEnums(true /*bVerbose*/); $sFeedback = ob_get_clean(); $this->log_ok("Meta enums rebuilt: $sFeedback"); // Restore the previous access mode $oConfig->Set('access_mode', $iPrevAccessMode); } else { if (strlen($oConfig->GetDBSubname()) > 0) { throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."' (prefix: '".$oConfig->GetDBSubname()."'). Please, try selecting another database instance."); } else { throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."'. Please, try selecting another database instance."); } } } return true; } public function UpdatePredefinedObjects() { // Have it work fine even if the DB has been set in read-only mode for the users $oConfig = MetaModel::GetConfig(); $iPrevAccessMode = $oConfig->Get('access_mode'); $oConfig->Set('access_mode', ACCESS_FULL); // Constant classes (e.g. User profiles) // foreach (MetaModel::GetClasses() as $sClass) { $aPredefinedObjects = call_user_func(array( $sClass, 'GetPredefinedObjects' )); if ($aPredefinedObjects != null) { $this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements."); // Create/Delete/Update objects of this class, // according to the given constant values // $aDBIds = array(); $oAll = new DBObjectSet(new DBObjectSearch($sClass)); while ($oObj = $oAll->Fetch()) { if (array_key_exists($oObj->GetKey(), $aPredefinedObjects)) { $aObjValues = $aPredefinedObjects[$oObj->GetKey()]; foreach ($aObjValues as $sAttCode => $value) { $oObj->Set($sAttCode, $value); } $oObj->DBUpdate(); $aDBIds[$oObj->GetKey()] = true; } else { $oObj->DBDelete(); } } foreach ($aPredefinedObjects as $iRefId => $aObjValues) { if (! array_key_exists($iRefId, $aDBIds)) { $oNewObj = MetaModel::NewObject($sClass); $oNewObj->SetKey($iRefId); foreach ($aObjValues as $sAttCode => $value) { $oNewObj->Set($sAttCode, $value); } $oNewObj->DBInsert(); } } } } // Restore the previous access mode $oConfig->Set('access_mode', $iPrevAccessMode); } public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sShortComment = null) { // Have it work fine even if the DB has been set in read-only mode for the users $iPrevAccessMode = $oConfig->Get('access_mode'); $oConfig->Set('access_mode', ACCESS_FULL); if (CMDBSource::DBName() == '') { // In case this has not yet been done CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName()); CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation()); } if ($sShortComment === null) { $sShortComment = 'Done by the setup program'; } $sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE; // Record datamodel version $aData = array( 'source_dir' => $oConfig->Get('source_dir'), ); $iInstallationTime = time(); // Make sure that all modules record the same installation time $oInstallRec = new ModuleInstallation(); $oInstallRec->Set('name', DATAMODEL_MODULE); $oInstallRec->Set('version', $sDataModelVersion); $oInstallRec->Set('comment', json_encode($aData)); $oInstallRec->Set('parent_id', 0); // root module $oInstallRec->Set('installed', $iInstallationTime); $iMainItopRecord = $oInstallRec->DBInsertNoReload(); // Record main installation $oInstallRec = new ModuleInstallation(); $oInstallRec->Set('name', ITOP_APPLICATION); $oInstallRec->Set('version', ITOP_VERSION.'.'.ITOP_REVISION); $oInstallRec->Set('comment', $sMainComment); $oInstallRec->Set('parent_id', 0); // root module $oInstallRec->Set('installed', $iInstallationTime); $iMainItopRecord = $oInstallRec->DBInsertNoReload(); // Record installed modules and extensions // $aAvailableExtensions = array(); $aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir()); foreach($aSelectedModuleCodes as $sModuleId) { $aModuleData = $aAvailableModules[$sModuleId]; $sName = $sModuleId; $sVersion = $aModuleData['version_code']; $aComments = array(); $aComments[] = $sShortComment; if ($aModuleData['mandatory']) { $aComments[] = 'Mandatory'; } else { $aComments[] = 'Optional'; } if ($aModuleData['visible']) { $aComments[] = 'Visible (during the setup)'; } else { $aComments[] = 'Hidden (selected automatically)'; } foreach ($aModuleData['dependencies'] as $sDependOn) { $aComments[] = "Depends on module: $sDependOn"; } $sComment = implode("\n", $aComments); $oInstallRec = new ModuleInstallation(); $oInstallRec->Set('name', $sName); $oInstallRec->Set('version', $sVersion); $oInstallRec->Set('comment', $sComment); $oInstallRec->Set('parent_id', $iMainItopRecord); $oInstallRec->Set('installed', $iInstallationTime); $oInstallRec->DBInsertNoReload(); } if ($this->oExtensionsMap) { // Mark as chosen the selected extensions code passed to us // Note: some other extensions may already be marked as chosen foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension) { if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) { $this->oExtensionsMap->MarkAsChosen($oExtension->sCode); } } foreach($this->oExtensionsMap->GetChoices() as $oExtension) { $oInstallRec = new ExtensionInstallation(); $oInstallRec->Set('code', $oExtension->sCode); $oInstallRec->Set('label', $oExtension->sLabel); $oInstallRec->Set('version', $oExtension->sVersion); $oInstallRec->Set('source', $oExtension->sSource); $oInstallRec->Set('installed', $iInstallationTime); $oInstallRec->DBInsertNoReload(); } } // Restore the previous access mode $oConfig->Set('access_mode', $iPrevAccessMode); // Database is created, installation has been tracked into it return true; } public function GetApplicationVersion(Config $oConfig) { $aResult = false; try { require_once(APPROOT.'/core/cmdbsource.class.inc.php'); CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName()); CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation()); $sSQLQuery = "SELECT * FROM ".$oConfig->GetDBSubname()."priv_module_install"; $aSelectInstall = CMDBSource::QueryToArray($sSQLQuery); } catch (MySQLException $e) { // No database or erroneous information $this->log_error('Can not connect to the database: host: '.$oConfig->GetDBHost().', user:'.$oConfig->GetDBUser().', pwd:'.$oConfig->GetDBPwd().', db name:'.$oConfig->GetDBName()); $this->log_error('Exception '.$e->getMessage()); return false; } // Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version foreach ($aSelectInstall as $aInstall) { $sModuleVersion = $aInstall['version']; if ($sModuleVersion == '') { // Though the version cannot be empty in iTop 2.0, it used to be possible // therefore we have to put something here or the module will not be considered // as being installed $sModuleVersion = '0.0.0'; } if ($aInstall['parent_id'] == 0) { if ($aInstall['name'] == DATAMODEL_MODULE) { $aResult['datamodel_version'] = $sModuleVersion; $aComments = json_decode($aInstall['comment'], true); if (is_array($aComments)) { $aResult = array_merge($aResult, $aComments); } } else { $aResult['product_name'] = $aInstall['name']; $aResult['product_version'] = $sModuleVersion; } } } if (!array_key_exists('datamodel_version', $aResult)) { // Versions prior to 2.0 did not record the version of the datamodel // so assume that the datamodel version is equal to the application version $aResult['datamodel_version'] = $aResult['product_version']; } $this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']); return $aResult; } public static function MakeDirSafe($sDir) { if (!is_dir($sDir)) { if (!@mkdir($sDir)) { throw new Exception("Failed to create directory '$sDir', please check that the web server process has enough rights to create the directory."); } @chmod($sDir, 0770); // RWX for owner and group, nothing for others } } /** * Wrappers for logging into the setup log files */ protected function log_error($sText) { SetupPage::log_error($sText); } protected function log_warning($sText) { SetupPage::log_warning($sText); } protected function log_info($sText) { SetupPage::log_info($sText); } protected function log_ok($sText) { SetupPage::log_ok($sText); } public function GetCurrentDataModelVersion() { $oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'"); $oSet = new DBObjectSet($oSearch, array(), array('installed' => false)); $oLatestDM = $oSet->Fetch(); if ($oLatestDM == null) { return '0.0.0'; } return $oLatestDM->Get('version'); } public function Commit() { if ($this->sFinalEnv != $this->sTargetEnv) { if (file_exists(APPROOT.'data/'.$this->sTargetEnv.'.delta.xml')) { if (file_exists(APPROOT.'data/'.$this->sFinalEnv.'.delta.xml')) { // Make a "previous" file copy( APPROOT.'data/'.$this->sTargetEnv.'.delta.xml', APPROOT.'data/'.$this->sFinalEnv.'.delta.prev.xml' ); } $this->CommitFile( APPROOT.'data/'.$this->sTargetEnv.'.delta.xml', APPROOT.'data/'.$this->sFinalEnv.'.delta.xml' ); } $this->CommitFile( APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml', APPROOT.'data/datamodel-'.$this->sFinalEnv.'.xml' ); $this->CommitFile( APPROOT.'data/datamodel-'.$this->sTargetEnv.'-with-delta.xml', APPROOT.'data/datamodel-'.$this->sFinalEnv.'-with-delta.xml', false ); $this->CommitDir( APPROOT.'data/'.$this->sTargetEnv.'-modules/', APPROOT.'data/'.$this->sFinalEnv.'-modules/', false ); $this->CommitDir( APPROOT.'data/cache-'.$this->sTargetEnv, APPROOT.'data/cache-'.$this->sFinalEnv, false ); $this->CommitDir( APPROOT.'env-'.$this->sTargetEnv, APPROOT.'env-'.$this->sFinalEnv, true, false ); // Move the config file // $sTargetConfig = APPCONF.$this->sTargetEnv.'/config-itop.php'; $sFinalConfig = APPCONF.$this->sFinalEnv.'/config-itop.php'; @chmod($sFinalConfig, 0770); // In case it exists: RWX for owner and group, nothing for others $this->CommitFile($sTargetConfig, $sFinalConfig); @chmod($sFinalConfig, 0440); // Read-only for owner and group, nothing for others @rmdir(dirname($sTargetConfig)); // Cleanup the temporary build dir if empty MetaModel::ResetCache(md5(APPROOT).'-'.$this->sFinalEnv); } } /** * Overwrite or create the destination file * * @param $sSource * @param $sDest * @param bool $bSourceMustExist * @throws Exception */ protected function CommitFile($sSource, $sDest, $bSourceMustExist = true) { if (file_exists($sSource)) { SetupUtils::builddir(dirname($sDest)); if (file_exists($sDest)) { $bRes = @unlink($sDest); if (!$bRes) { throw new Exception('Commit - Failed to cleanup destination file: '.$sDest); } } rename($sSource, $sDest); } else { // The file does not exist if ($bSourceMustExist) { throw new Exception('Commit - Missing file: '.$sSource); } else { // Align the destination with the source... make sure there is NO file if (file_exists($sDest)) { $bRes = @unlink($sDest); if (!$bRes) { throw new Exception('Commit - Failed to cleanup destination file: '.$sDest); } } } } } /** * Overwrite or create the destination directory * * @param $sSource * @param $sDest * @param boolean $bSourceMustExist * @param boolean $bRemoveSource If true $sSource will be removed, otherwise $sSource will just be emptied * @throws Exception */ protected function CommitDir($sSource, $sDest, $bSourceMustExist = true, $bRemoveSource = true) { if (file_exists($sSource)) { SetupUtils::movedir($sSource, $sDest, $bRemoveSource); } else { // The file does not exist if ($bSourceMustExist) { throw new Exception('Commit - Missing directory: '.$sSource); } else { // Align the destination with the source... make sure there is NO file if (file_exists($sDest)) { SetupUtils::rrmdir($sDest); } } } } } // End of class