diff --git a/application/utils.inc.php b/application/utils.inc.php index 2097b3c28..229d41d4c 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -615,16 +615,20 @@ class utils { if (self::$oConfig == null) { - $sConfigFile = self::GetConfigFilePath(); - if (file_exists($sConfigFile)) - { - self::$oConfig = new Config($sConfigFile); - } - else - { - // When executing the setup, the config file may be still missing - self::$oConfig = new Config(); - } + self::$oConfig = MetaModel::GetConfig(); + if (self::$oConfig == null) + { + $sConfigFile = self::GetConfigFilePath(); + if (file_exists($sConfigFile)) + { + self::$oConfig = new Config($sConfigFile); + } + else + { + // When executing the setup, the config file may be still missing + self::$oConfig = new Config(); + } + } } return self::$oConfig; } diff --git a/core/sqlobjectquery.class.inc.php b/core/sqlobjectquery.class.inc.php index 92320aa5e..72290f0c0 100644 --- a/core/sqlobjectquery.class.inc.php +++ b/core/sqlobjectquery.class.inc.php @@ -547,7 +547,7 @@ class SQLObjectQuery extends SQLQuery return $iRet; } - protected function CollectUsedTables(&$aTables) + public function CollectUsedTables(&$aTables) { $this->m_oConditionExpr->CollectUsedParents($aTables); foreach($this->m_aFields as $sFieldAlias => $oField) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 6abc25cc2..aff9b63d7 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -1157,7 +1157,7 @@ class UserRights self::$m_aAdmins = array(); self::$m_aPortalUsers = array(); } - if (!isset($_SESSION)) + if (!isset($_SESSION) && !utils::IsModeCLI()) { session_name('itop-'.md5(APPROOT)); session_start(); diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index 2ea206e30..c0131193b 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -240,8 +240,9 @@ class ApplicationInstaller $sDBName = $aDBParams['name']; $sDBPrefix = $aDBParams['prefix']; $bOldAddon = $this->oParams->Get('old_addon', false); + $sUrl = $this->oParams->Get('url', ''); - self::DoUpdateDBSchema($sMode, $aSelectedModules, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment, $bOldAddon); + self::DoUpdateDBSchema($sMode, $aSelectedModules, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment, $bOldAddon, $sUrl); $aResult = array( 'status' => self::OK, @@ -555,7 +556,7 @@ class ApplicationInstaller } } - protected static function DoUpdateDBSchema($sMode, $aSelectedModules, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '', $bOldAddon = false) + protected static function DoUpdateDBSchema($sMode, $aSelectedModules, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '', $bOldAddon = false, $sAppRootUrl = '') { SetupPage::log_info("Update Database Schema for environment '$sTargetEnvironment'."); @@ -568,6 +569,7 @@ class ApplicationInstaller 'db_pwd' => $sDBPwd, 'db_name' => $sDBName, 'db_prefix' => $sDBPrefix, + 'application_path' => $sAppRootUrl, ); $oConfig->UpdateFromParams($aParamValues, $sModulesDir); if ($bOldAddon) @@ -577,7 +579,7 @@ class ApplicationInstaller 'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php', )); } - + $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); $oProductionEnv->InitDataModel($oConfig, true); // load data model only @@ -636,17 +638,7 @@ class ApplicationInstaller // Module specific actions (migrate the data) // $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir); - foreach($aAvailableModules as $sModuleId => $aModule) - { - if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && - isset($aAvailableModules[$sModuleId]['installer']) ) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - SetupPage::log_info("Calling Module Handler: $sModuleInstallerClass::BeforeDatabaseCreation(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); - $aCallSpec = array($sModuleInstallerClass, 'BeforeDatabaseCreation'); - call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); - } - } + $oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation'); if(!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) { @@ -778,18 +770,7 @@ class ApplicationInstaller // Perform here additional DB setup... profiles, etc... // $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir); - foreach($aAvailableModules as $sModuleId => $aModule) - { - if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && - isset($aAvailableModules[$sModuleId]['installer']) ) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - SetupPage::log_info("Calling Module Handler: $sModuleInstallerClass::AfterDatabaseCreation(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); - // The validity of the sModuleInstallerClass has been established in BuildConfig() - $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseCreation'); - call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); - } - } + $oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation'); $oProductionEnv->UpdatePredefinedObjects(); @@ -807,18 +788,7 @@ class ApplicationInstaller // Perform final setup tasks here // - foreach($aAvailableModules as $sModuleId => $aModule) - { - if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && - isset($aAvailableModules[$sModuleId]['installer']) ) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - SetupPage::log_info("Calling Module Handler: $sModuleInstallerClass::AfterDatabaseSetup(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); - // The validity of the sModuleInstallerClass has been established in BuildConfig() - $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseSetup'); - call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); - } - } + $oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup'); } /** @@ -860,131 +830,21 @@ class ApplicationInstaller )); } + $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); + //Load the MetaModel if needed (asynchronous mode) if (!self::$bMetaModelStarted) { - $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); $oProductionEnv->InitDataModel($oConfig, false); // load data model and connect to the database self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously } - - $oDataLoader = new XMLDataLoader(); - - CMDBObject::SetTrackInfo("Initialization"); - $oMyChange = CMDBObject::GetCurrentChange(); - - SetupPage::log_info("starting data load session"); - $oDataLoader->StartSession($oMyChange); - - $aFiles = array(); - $aPreviouslyLoadedFiles = array(); - $oProductionEnv = new RunTimeEnvironment(); $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, APPROOT.$sModulesDir); - foreach($aAvailableModules as $sModuleId => $aModule) - { - if (($sModuleId != ROOT_MODULE)) - { - $sRelativePath = 'env-'.$sTargetEnvironment.'/'.basename($aModule['root_dir']); - // Load data only for selected AND newly installed modules - if (in_array($sModuleId, $aSelectedModules)) - { - if ($aModule['version_db'] != '') - { - // Simulate the load of the previously loaded XML files to get the mapping of the keys - if ($bSampleData) - { - $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); - } - else - { - // Load only structural data - $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - } - } - else - { - if ($bSampleData) - { - $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); - } - else - { - // Load only structural data - $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - } - } - } - } - } + $oProductionEnv->LoadData($aAvailableModules, $aSelectedModules, $bSampleData); - // Simulate the load of the previously loaded files, in order to initialize - // the mapping between the identifiers in the XML and the actual identifiers - // in the current database - foreach($aPreviouslyLoadedFiles as $sFileRelativePath) - { - $sFileName = APPROOT.$sFileRelativePath; - SetupPage::log_info("Loading file: $sFileName (just to get the keys mapping)"); - if (empty($sFileName) || !file_exists($sFileName)) - { - throw(new Exception("File $sFileName does not exist")); - } - - $oDataLoader->LoadFile($sFileName, true); - $sResult = sprintf("loading of %s done.", basename($sFileName)); - SetupPage::log_info($sResult); - } - - foreach($aFiles as $sFileRelativePath) - { - $sFileName = APPROOT.$sFileRelativePath; - SetupPage::log_info("Loading file: $sFileName"); - if (empty($sFileName) || !file_exists($sFileName)) - { - throw(new Exception("File $sFileName does not exist")); - } - - $oDataLoader->LoadFile($sFileName); - $sResult = sprintf("loading of %s done.", basename($sFileName)); - SetupPage::log_info($sResult); - } - - $oDataLoader->EndSession(); - SetupPage::log_info("ending data load session"); - - // Perform after dbload setup tasks here - // - foreach($aAvailableModules as $sModuleId => $aModule) - { - if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && - isset($aAvailableModules[$sModuleId]['installer']) ) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - SetupPage::log_info("Calling Module Handler: $sModuleInstallerClass::AfterDataLoad(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); - // The validity of the sModuleInstallerClass has been established in BuildConfig() - $aCallSpec = array($sModuleInstallerClass, 'AfterDataLoad'); - call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); - } - } - } - - /** - * Merge two arrays of file names, adding the relative path to the files provided in the array to merge - * @param string[] $aSourceArray - * @param string $sBaseDir - * @param string[] $aFilesToMerge - * @return string[] - */ - protected static function MergeWithRelativeDir($aSourceArray, $sBaseDir, $aFilesToMerge) - { - $aToMerge = array(); - foreach($aFilesToMerge as $sFile) - { - $aToMerge[] = $sBaseDir.'/'.$sFile; - } - return array_merge($aSourceArray, $aToMerge); + // Perform after dbload setup tasks here + // + $oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad'); } protected static function DoCreateConfig($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sTargetEnvironment, $bOldAddon, $sSourceDir, $sPreviousConfigFile, $sDataModelVersion, $sGraphvizPath) diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php index a02dbe77f..520b37ad0 100644 --- a/setup/extensionsmap.class.inc.php +++ b/setup/extensionsmap.class.inc.php @@ -67,6 +67,16 @@ class iTopExtension */ public $aModules; + /** + * @var string[] + */ + public $aModuleVersion; + + /** + * @var string + */ + public $sSourceDir; + public function __construct() { $this->sCode = ''; @@ -78,6 +88,8 @@ class iTopExtension $this->bMarkedAsChosen = false; $this->sVersion = ITOP_VERSION; $this->sInstalledVersion = ''; + $this->aModuleVersion = array(); + $this->sSourceDir = ''; $this->bVisible = true; } } @@ -240,6 +252,7 @@ class iTopExtensionsMap $oExtension->sMoreInfoUrl = $oXml->Get('more_info_url'); $oExtension->sVersion = $oXml->Get('version'); $oExtension->sSource = $sSource; + $oExtension->sSourceDir = $sSearchDir; $sParentExtensionId = $sExtensionId = $oExtension->sCode.'/'.$oExtension->sVersion; $this->AddExtension($oExtension); @@ -274,7 +287,8 @@ class iTopExtensionsMap { // Already inside an extension, let's add this module the list of modules belonging to this extension $this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName; - } + $this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion; + } else { // Not already inside an folder containing an 'extension.xml' file @@ -299,10 +313,10 @@ class iTopExtensionsMap $oExtension->bMandatory = $aModuleInfo[2]['mandatory']; $oExtension->sMoreInfoUrl = $aModuleInfo[2]['doc.more_information']; $oExtension->aModules = array($sModuleName); + $oExtension->aModuleVersion[$sModuleName] = $sModuleVersion; + $oExtension->sSourceDir = $sSearchDir; $oExtension->bVisible = $bVisible; - - $this->AddExtension($oExtension); - + $this->AddExtension($oExtension); } } } @@ -479,6 +493,34 @@ class iTopExtensionsMap return true; } + /** + * Find is a single-module extension is contained within another extension + * @param iTopExtension $oExtension + * @return NULL|iTopExtension + */ + public function IsExtensionObsoletedByAnother(iTopExtension $oExtension) + { + // Complex extensions (more than 1 module) are never considered as obsolete + if (count($oExtension->aModules) != 1) return null; + + foreach($this->GetAllExtensions() as $oOtherExtension) + { + if (($oOtherExtension->sSourceDir != $oExtension->sSourceDir) && ($oOtherExtension->sSource != iTopExtension::SOURCE_WIZARD)) + { + if (array_key_exists($oExtension->sCode, $oOtherExtension->aModuleVersion) && + (version_compare($oOtherExtension->aModuleVersion[$oExtension->sCode], $oExtension->sVersion, '>=')) ) + { + // Found another extension containing a more recent version of the extension/module + return $oOtherExtension; + } + } + } + + // No match at all + return null; + + } + /** * Search for multi-module extensions that are NOT deployed as an extension (i.e. shipped with an extension.xml file) * but as a bunch of un-related modules based on the signature of some well-known extensions. If such an extension is found, diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 4acd8af39..56874b7e6 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -482,6 +482,7 @@ class RunTimeEnvironment * - 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 + * @return string[] */ public function CompileFrom($sSourceEnv, $bUseSymLinks = false) { @@ -492,7 +493,8 @@ class RunTimeEnvironment // Do load the required modules // $oFactory = new ModelFactory($sSourceDirFull); - foreach($this->GetMFModulesToCompile($sSourceEnv, $sSourceDir) as $oModule) + $aModulesToCompile = $this->GetMFModulesToCompile($sSourceEnv, $sSourceDir); + foreach($aModulesToCompile as $oModule) { if ($oModule instanceof MFDeltaModule) { @@ -543,6 +545,7 @@ class RunTimeEnvironment MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv); } + return array_keys($aModulesToCompile); } /** @@ -1042,4 +1045,174 @@ class RunTimeEnvironment } } } + + /** + * Call the given handler method for all selected modules having an installation handler + * @param array[] $aAvailableModules + * @param string[] $aSelectedModules + * @param string $sHandlerName + */ + public function CallInstallerHandlers($aAvailableModules, $aSelectedModules, $sHandlerName) + { + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && + isset($aAvailableModules[$sModuleId]['installer']) ) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + SetupPage::log_info("Calling Module Handler: $sModuleInstallerClass::$sHandlerName(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); + $aCallSpec = array($sModuleInstallerClass, $sHandlerName); + if (is_callable($aCallSpec)) + { + call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); + } + } + } + } + + /** + * Load data from XML files for the selected modules (structural data and/or sample data) + * @param array[] $aAvailableModules All available modules and their definition + * @param string[] $aSelectedModules List of selected modules + * @param bool $bSampleData Wether or not to load sample data + */ + public function LoadData($aAvailableModules, $aSelectedModules, $bSampleData) + { + $oDataLoader = new XMLDataLoader(); + + CMDBObject::SetTrackInfo("Initialization"); + $oMyChange = CMDBObject::GetCurrentChange(); + + SetupPage::log_info("starting data load session"); + $oDataLoader->StartSession($oMyChange); + + $aFiles = array(); + $aPreviouslyLoadedFiles = array(); + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != ROOT_MODULE)) + { + $sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']); + // Load data only for selected AND newly installed modules + if (in_array($sModuleId, $aSelectedModules)) + { + if ($aModule['version_db'] != '') + { + // Simulate the load of the previously loaded XML files to get the mapping of the keys + if ($bSampleData) + { + $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); + } + else + { + // Load only structural data + $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + } + } + else + { + if ($bSampleData) + { + $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); + } + else + { + // Load only structural data + $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + } + } + } + } + } + + // Simulate the load of the previously loaded files, in order to initialize + // the mapping between the identifiers in the XML and the actual identifiers + // in the current database + foreach($aPreviouslyLoadedFiles as $sFileRelativePath) + { + $sFileName = APPROOT.$sFileRelativePath; + SetupPage::log_info("Loading file: $sFileName (just to get the keys mapping)"); + if (empty($sFileName) || !file_exists($sFileName)) + { + throw(new Exception("File $sFileName does not exist")); + } + + $oDataLoader->LoadFile($sFileName, true); + $sResult = sprintf("loading of %s done.", basename($sFileName)); + SetupPage::log_info($sResult); + } + + foreach($aFiles as $sFileRelativePath) + { + $sFileName = APPROOT.$sFileRelativePath; + SetupPage::log_info("Loading file: $sFileName"); + if (empty($sFileName) || !file_exists($sFileName)) + { + throw(new Exception("File $sFileName does not exist")); + } + + $oDataLoader->LoadFile($sFileName); + $sResult = sprintf("loading of %s done.", basename($sFileName)); + SetupPage::log_info($sResult); + } + + $oDataLoader->EndSession(); + SetupPage::log_info("ending data load session"); + } + + /** + * Merge two arrays of file names, adding the relative path to the files provided in the array to merge + * @param string[] $aSourceArray + * @param string $sBaseDir + * @param string[] $aFilesToMerge + * @return string[] + */ + protected static function MergeWithRelativeDir($aSourceArray, $sBaseDir, $aFilesToMerge) + { + $aToMerge = array(); + foreach($aFilesToMerge as $sFile) + { + $aToMerge[] = $sBaseDir.'/'.$sFile; + } + return array_merge($aSourceArray, $aToMerge); + } + + /** + * Check the MetaModel for some common pitfall (class name too long, classes requiring too many joins...) + * The check takes about 900 ms for 200 classes + * @throws Exception + * @return string + */ + public function CheckMetaModel() + { + $iCount = 0; + $fStart = microtime(true); + foreach(MetaModel::GetClasses() as $sClass) + { + $oSearch = new DBObjectSearch($sClass); + $oSearch->SetShowObsoleteData(false); + $oSQLQuery = $oSearch->GetSQLQueryStructure(null, false); + $sViewName = MetaModel::DBGetView($sClass); + if (strlen($sViewName) > 64) + { + throw new Exception("Class name too long for class: '$sClass'. The name of the corresponding view ($sViewName) would exceed MySQL's limit for the name of a table (64 characters)."); + } + $sTableName = MetaModel::DBGetTable($sClass); + if (strlen($sTableName) > 64) + { + throw new Exception("Table name too long for class: '$sClass'. The name of the corresponding MySQL table ($sTableName) would exceed MySQL's limit for the name of a table (64 characters)."); + } + $iTableCount = $oSQLQuery->CountTables(); + if ($iTableCount > 61) + { + throw new Exception("Class requiring too many tables: '$sClass'. The structure of the class ($sClass) would require a query with more than 61 JOINS (MySQL's limitation)."); + } + $iCount++; + } + $fDuration = microtime(true) - $fStart; + + return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0); + } } // End of class