diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index ca4836085..448eac1e2 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -3,7 +3,7 @@ // // This file is part of iTop. // -// iTop is free software; you can redistribute it and/or modify +// iTop is free software; you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. @@ -25,7 +25,7 @@ require_once(APPROOT.'setup/backup.class.inc.php'); * The installation process is split into a sequence of unitary steps * for performance reasons (i.e; timeout, memory usage) and also in order * to provide some feedback about the progress of the installation. - * + * * This class can be used for a step by step interactive installation * while displaying a progress bar, or in an unattended manner * (for example from the command line), to run all the steps @@ -157,7 +157,7 @@ class ApplicationInstaller } } while(($aRes['status'] != self::ERROR) && ($aRes['next-step'] != '')); - + return ($iOverallStatus == self::OK); } @@ -228,7 +228,7 @@ class ApplicationInstaller case 'copy': $aPreinstall = $this->oParams->Get('preinstall'); - $aCopies = $aPreinstall['copies']; + $aCopies = $aPreinstall['copies'] ?? []; self::DoCopy($aCopies); $sReport = "Copying..."; @@ -473,7 +473,7 @@ class ApplicationInstaller { $sSource = $aCopy['source']; $sDestination = APPROOT.$aCopy['destination']; - + SetupUtils::builddir($sDestination); SetupUtils::tidydir($sDestination); SetupUtils::copydir($sSource, $sDestination); @@ -598,10 +598,10 @@ class ApplicationInstaller } $oFactory = new ModelFactory($aDirsToScan); - + $oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries'); $oFactory->LoadModule($oDictModule); - + $sDeltaFile = APPROOT.'core/datamodel.core.xml'; if (file_exists($sDeltaFile)) { @@ -614,7 +614,7 @@ class ApplicationInstaller $oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile); $oFactory->LoadModule($oApplicationModule); } - + $aModules = $oFactory->FindModules(); foreach($aModules as $oModule) @@ -627,7 +627,7 @@ class ApplicationInstaller } // Dump the "reference" model, just before loading any actual delta $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$sEnvironment.'.xml'); - + $sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml'; if (file_exists($sDeltaFile)) { @@ -651,12 +651,12 @@ class ApplicationInstaller if (file_exists($sFileToPatch)) { $sContent = file_get_contents($sFileToPatch); - + $sContent = str_replace("require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", "//\n// The line below is no longer needed in iTop 2.0 -- patched by the setup program\n// require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", $sContent); - + file_put_contents($sFileToPatch, $sContent); } - + // Set an "Instance UUID" identifying this machine based on a file located in the data directory $sInstanceUUIDFile = APPROOT.'data/instance.txt'; SetupUtils::builddir(APPROOT.'data'); @@ -732,7 +732,7 @@ class ApplicationInstaller { SetupLog::Info("Renaming '{$sDBPrefix}priv_internalUser' failed (already done in a previous upgrade?)"); } - + // let's remove the records in priv_change which have no counterpart in priv_changeop SetupLog::Info("Cleanup of '{$sDBPrefix}priv_change' to remove orphan records"); CMDBSource::SelectDB($sDBName); @@ -741,7 +741,7 @@ class ApplicationInstaller $sTotalCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change`"; $iTotalCount = (int)CMDBSource::QueryToScalar($sTotalCount); SetupLog::Info("There is a total of $iTotalCount records in {$sDBPrefix}priv_change."); - + $sOrphanCount = "SELECT COUNT(c.id) FROM `{$sDBPrefix}priv_change` AS c left join `{$sDBPrefix}priv_changeop` AS o ON c.id = o.changeid WHERE o.id IS NULL"; $iOrphanCount = (int)CMDBSource::QueryToScalar($sOrphanCount); SetupLog::Info("There are $iOrphanCount useless records in {$sDBPrefix}priv_change (".sprintf('%.2f', ((100.0*$iOrphanCount)/$iTotalCount))."%)"); @@ -767,9 +767,9 @@ class ApplicationInstaller { SetupLog::Info("Cleanup of orphan records in `{$sDBPrefix}priv_change` failed: ".$e->getMessage()); } - + } - + // Module specific actions (migrate the data) // $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir); @@ -777,9 +777,9 @@ class ApplicationInstaller if(!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) { - throw new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'"); + throw new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'"); } - + // Set a DBProperty with a unique ID to identify this instance of iTop $sUUID = DBProperty::GetProperty('database_uuid', ''); if ($sUUID === '') @@ -787,10 +787,10 @@ class ApplicationInstaller $sUUID = utils::CreateUUID('database'); DBProperty::SetProperty('database_uuid', $sUUID, 'Installation/upgrade of '.ITOP_APPLICATION, 'Unique ID of this '.ITOP_APPLICATION.' Database'); } - + // priv_change now has an 'origin' field to distinguish between the various input sources // Let's initialize the field with 'interactive' for all records were it's null - // Then check if some records should hold a different value, based on a pattern matching in the userinfo field + // Then check if some records should hold a different value, based on a pattern matching in the userinfo field CMDBSource::SelectDB($sDBName); try { @@ -799,20 +799,20 @@ class ApplicationInstaller if ($iCount > 0) { SetupLog::Info("Initializing '{$sDBPrefix}priv_change.origin' ($iCount records to update)"); - + // By default all uninitialized values are considered as 'interactive' $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'interactive' WHERE `origin` IS NULL"; CMDBSource::Query($sInit); - + // CSV Import was identified by the comment at the end $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-import.php' WHERE `userinfo` LIKE '%Web Service (CSV)'"; CMDBSource::Query($sInit); - + // CSV Import was identified by the comment at the end $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-interactive' WHERE `userinfo` LIKE '%(CSV)' AND origin = 'interactive'"; CMDBSource::Query($sInit); - - + + // Syncho data sources were identified by the comment at the end // Unfortunately the comment is localized, so we have to search for all possible patterns $sCurrentLanguage = Dict::GetUserLanguage(); @@ -826,7 +826,7 @@ class ApplicationInstaller Dict::SetUserLanguage($sCurrentLanguage); $sCondition = "`userinfo` LIKE ".implode(" OR `userinfo` LIKE ", array_keys($aSuffixes)); - $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'synchro-data-source' WHERE ($sCondition)"; + $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'synchro-data-source' WHERE ($sCondition)"; CMDBSource::Query($sInit); SetupLog::Info("Initialization of '{$sDBPrefix}priv_change.origin' completed."); @@ -851,7 +851,7 @@ class ApplicationInstaller if ($iCount > 0) { SetupLog::Info("Initializing '{$sDBPrefix}priv_async_task.status' ($iCount records to update)"); - + $sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'planned' WHERE (`status` IS NULL) AND (`started` IS NULL)"; CMDBSource::Query($sInit); @@ -911,15 +911,16 @@ class ApplicationInstaller $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); $oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database + $oContextTag = new ContextTag(ContextTag::TAG_SETUP); self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously - + // Perform here additional DB setup... profiles, etc... // $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir); $oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation'); $oProductionEnv->UpdatePredefinedObjects(); - + if($sMode == 'install') { if (!self::CreateAdminAccount(MetaModel::GetConfig(), $sAdminUser, $sAdminPwd, $sAdminLanguage)) @@ -931,20 +932,20 @@ class ApplicationInstaller SetupLog::Info("Administrator account '$sAdminUser' created."); } } - + // Perform final setup tasks here // $oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup'); } - + /** * Helper function to create and administrator account for iTop - * @return boolean true on success, false otherwise + * @return boolean true on success, false otherwise */ protected static function CreateAdminAccount(Config $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) { SetupLog::Info('CreateAdminAccount'); - + if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage)) { return true; @@ -976,17 +977,17 @@ class ApplicationInstaller 'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php', )); } - + $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); - + //Load the MetaModel if needed (asynchronous mode) if (!self::$bMetaModelStarted) { $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 - } - + } + $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, APPROOT.$sModulesDir); $oProductionEnv->LoadData($aAvailableModules, $aSelectedModules, $bSampleData); @@ -1027,7 +1028,7 @@ class ApplicationInstaller $bPreserveModuleSettings = false; if ($sMode == 'upgrade') { - try + try { $oOldConfig = new Config($sPreviousConfigFile); $oConfig = clone($oOldConfig); @@ -1082,7 +1083,7 @@ class ApplicationInstaller @chmod($sConfigDir, 0770); // RWX for owner and group, nothing for others $oConfig->WriteToFile($sConfigFile); - + // try to make the final config file read-only @chmod($sConfigFile, 0440); // Read-only for owner and group, nothing for others diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 7f7a93b40..cca77175a 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -3,7 +3,7 @@ // // This file is part of iTop. // -// iTop is free software; you can redistribute it and/or modify +// iTop is free software; you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. @@ -100,14 +100,14 @@ class RunTimeEnvironment $this->log_info(sprintf('%.3fs - query: %s ', $fDuration, $sQuery)); $this->log_db_query($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 + * @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.'/setup/moduleinstallation.class.inc.php'; @@ -121,15 +121,15 @@ class RunTimeEnvironment { $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); @@ -139,7 +139,7 @@ class RunTimeEnvironment /** * Analyzes the current installation and the possibilities * - * @param Config $oConfig Defines the target environment (DB) + * @param null|Config $oConfig Defines the target environment (DB) * @param mixed $modulesPath Either a single string or an array of absolute paths * @param bool $bAbortOnMissingDependency ... * @param array $aModulesToLoad List of modules to search for, defaults to all if omitted @@ -178,7 +178,7 @@ class RunTimeEnvironment 'name_code' => ITOP_APPLICATION, ) ); - + $aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath); $aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad); foreach($aModules as $sModuleId => $aModuleInfo) @@ -194,11 +194,11 @@ class RunTimeEnvironment //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 @@ -223,18 +223,20 @@ class RunTimeEnvironment } $aRes[$sModuleName] = $aModuleInfo; } - + try { - CMDBSource::InitFromConfig($oConfig); - $aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install"); + $aSelectInstall = array(); + if (! is_null($oConfig)) { + CMDBSource::InitFromConfig($oConfig); + $aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."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' => ) @@ -251,7 +253,7 @@ class RunTimeEnvironment } } } - + foreach ($aSelectInstall as $aInstall) { //$aInstall['comment']; // unsused @@ -265,7 +267,7 @@ class RunTimeEnvironment // as being installed $sModuleVersion = '0.0.0'; } - + if ($aInstall['parent_id'] == 0) { $sModuleName = ROOT_MODULE; @@ -275,7 +277,7 @@ class RunTimeEnvironment // Skip all modules belonging to previous installations continue; } - + if (array_key_exists($sModuleName, $aInstallByModule)) { if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) @@ -283,30 +285,30 @@ class RunTimeEnvironment 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 + // 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( @@ -322,7 +324,7 @@ class RunTimeEnvironment ); } } - + return $aRes; } @@ -336,9 +338,9 @@ class RunTimeEnvironment { 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); @@ -354,7 +356,7 @@ class RunTimeEnvironment // Do nothing, overload this method if needed return array(); } - + /** * Decide whether or not the given extension is selected for installation * @param iTopExtension $oExtension @@ -364,10 +366,10 @@ class RunTimeEnvironment { return ($oExtension->sSource == iTopExtension::SOURCE_REMOTE); } - + /** - * Get the installed modules (only the installed ones) - */ + * Get the installed modules (only the installed ones) + */ protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) { $sSourceDirFull = APPROOT.$sSourceDir; @@ -388,7 +390,7 @@ class RunTimeEnvironment $aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile); $aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs); - + $aRet = array(); // Determine the installed modules and extensions @@ -396,7 +398,7 @@ class RunTimeEnvironment $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 @@ -416,7 +418,7 @@ class RunTimeEnvironment // $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)) @@ -430,14 +432,14 @@ class RunTimeEnvironment $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 = $this->oExtensionsMap->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE); - if (array_key_exists($sModule, $aAvailableModules)) + if (array_key_exists($sModule, $aAvailableModules)) { if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) //Extra modules are always unless they are 'AutoSelect' { @@ -445,7 +447,7 @@ class RunTimeEnvironment } } } - + // Now process the 'AutoSelect' modules do { @@ -564,7 +566,7 @@ class RunTimeEnvironment { $this->log_info("Creating the structure in '".$oConfig->Get('db_name')."'."); } - + //MetaModel::CheckDefinitions(); if ($sMode == 'install') { @@ -596,7 +598,7 @@ class RunTimeEnvironment MetaModel::DBCreate(array($this, 'LogQueryCallback')); $this->log_ok("Database structure successfully updated."); - + // Check (and update only if it seems needed) the hierarchical keys if (MFCompiler::SkipRebuildHKeys()) { $this->log_ok("Hierchical keys are NOT rebuilt due to the presence of the \"data/.setup-rebuild-hkeys-never\" file"); @@ -656,7 +658,7 @@ class RunTimeEnvironment 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 // @@ -698,7 +700,7 @@ class RunTimeEnvironment // 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 @@ -707,7 +709,7 @@ class RunTimeEnvironment //$oConfig->Set('access_mode', ACCESS_FULL); if (CMDBSource::DBName() == '') - { + { // In case this has not yet been done CMDBSource::InitFromConfig($oConfig); } @@ -717,7 +719,7 @@ class RunTimeEnvironment $sShortComment = 'Done by the setup program'; } $sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE; - + // Record datamodel version $aData = array( 'source_dir' => $oConfig->Get('source_dir'), @@ -730,7 +732,7 @@ class RunTimeEnvironment $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); @@ -793,7 +795,7 @@ class RunTimeEnvironment $this->oExtensionsMap->MarkAsChosen($oExtension->sCode); } } - + foreach($this->oExtensionsMap->GetChoices() as $oExtension) { $oInstallRec = new ExtensionInstallation(); @@ -810,7 +812,7 @@ class RunTimeEnvironment MetaModel::GetConfig()->Set('access_mode', $iPrevAccessMode); // Database is created, installation has been tracked into it - return true; + return true; } /** @@ -846,7 +848,7 @@ class RunTimeEnvironment // as being installed $sModuleVersion = '0.0.0'; } - + if ($aInstall['parent_id'] == 0) { if ($aInstall['name'] == DATAMODEL_MODULE) @@ -888,8 +890,8 @@ class RunTimeEnvironment } /** - * Wrappers for logging into the setup log files - */ + * Wrappers for logging into the setup log files + */ protected function log_error($sText) { SetupLog::Error($sText); @@ -925,7 +927,7 @@ class RunTimeEnvironment fclose($hSetupQueriesFile); } } - + public function GetCurrentDataModelVersion() { $oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'"); @@ -1118,7 +1120,7 @@ class RunTimeEnvironment } } } - + /** * Load data from XML files for the selected modules (structural data and/or sample data) * @param array[] $aAvailableModules All available modules and their definition @@ -1170,7 +1172,7 @@ class RunTimeEnvironment } } } - + // 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 @@ -1182,12 +1184,12 @@ class RunTimeEnvironment { throw(new Exception("File $sFileName does not exist")); } - + $oDataLoader->LoadFile($sFileName, true); $sResult = sprintf("loading of %s done.", basename($sFileName)); SetupLog::Info($sResult); } - + foreach($aFiles as $sFileRelativePath) { $sFileName = APPROOT.$sFileRelativePath; @@ -1196,16 +1198,16 @@ class RunTimeEnvironment { throw(new Exception("File $sFileName does not exist")); } - + $oDataLoader->LoadFile($sFileName); $sResult = sprintf("loading of %s done.", basename($sFileName)); SetupLog::Info($sResult); } - + $oDataLoader->EndSession(); SetupLog::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 @@ -1222,7 +1224,7 @@ class RunTimeEnvironment } 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 @@ -1262,7 +1264,7 @@ class RunTimeEnvironment $iCount++; } $fDuration = microtime(true) - $fStart; - + return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0); } } // End of class diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php index eb2089255..88150127a 100644 --- a/setup/setuputils.class.inc.php +++ b/setup/setuputils.class.inc.php @@ -818,7 +818,7 @@ class SetupUtils { if (!is_dir($sDest)) { - mkdir($sDest); + mkdir($sDest, 0777 /* Default */, true); } $aFiles = scandir($sSource); if(sizeof($aFiles) > 0 ) diff --git a/setup/unattended-install/InstallationFileService.php b/setup/unattended-install/InstallationFileService.php new file mode 100644 index 000000000..dc2ff7abd --- /dev/null +++ b/setup/unattended-install/InstallationFileService.php @@ -0,0 +1,244 @@ +sInstallationPath = $sInstallationPath; + $this->aSelectedModules = []; + $this->aUnSelectedModules = []; + $this->sTargetEnvironment = $sTargetEnvironment; + $this->aSelectedExtensions = $aSelectedExtensions; + $this->bInstallationOptionalChoicesChecked = $bInstallationOptionalChoicesChecked; + } + + public function GetSelectedModules(): array { + return $this->aSelectedModules; + } + + public function GetUnSelectedModules(): array { + return $this->aUnSelectedModules; + } + + public function Init(): void { + clearstatcache(); + + $this->ProcessDefaultModules(); + $this->ProcessInstallationChoices(); + $this->ProcessAutoSelectModules(); + } + + public function ProcessInstallationChoices(): void { + $oXMLParameters = new XMLParameters($this->sInstallationPath); + $aSteps = $oXMLParameters->Get('steps', []); + if (! is_array($aSteps)) { + return; + } + + foreach ($aSteps as $aStepInfo) { + $aOptions = $aStepInfo["options"] ?? null; + if (! is_null($aOptions) && is_array($aOptions)) { + foreach ($aOptions as $aChoiceInfo) { + $this->ProcessSelectedChoice($aChoiceInfo, $this->bInstallationOptionalChoicesChecked); + } + } + $aOptions = $aStepInfo["alternatives"] ?? null; + if (! is_null($aOptions) && is_array($aOptions)) { + foreach ($aOptions as $aChoiceInfo) { + $this->ProcessSelectedChoice($aChoiceInfo, false); + } + } + } + + foreach ($this->aSelectedModules as $sModuleId => $sVal){ + if (array_key_exists($sModuleId, $this->aUnSelectedModules)){ + unset($this->aUnSelectedModules[$sModuleId]); + } + } + } + + private function ProcessUnSelectedChoice($aChoiceInfo) { + if (!is_array($aChoiceInfo)) { + return; + } + + $aCurrentModules = $aChoiceInfo["modules"] ?? []; + foreach ($aCurrentModules as $sModuleId){ + $this->aUnSelectedModules[$sModuleId] = true; + } + + $aAlternatives = $aChoiceInfo["alternatives"] ?? null; + if (!is_null($aAlternatives) && is_array($aAlternatives)) { + foreach ($aAlternatives as $aSubChoiceInfo) { + $this->ProcessUnSelectedChoice($aSubChoiceInfo); + } + } + + if (array_key_exists('sub_options', $aChoiceInfo)) { + if (array_key_exists('options', $aChoiceInfo['sub_options'])) { + $aSubOptions = $aChoiceInfo['sub_options']['options']; + if (!is_null($aSubOptions) && is_array($aSubOptions)) { + foreach ($aSubOptions as $aSubChoiceInfo) { + $this->ProcessUnSelectedChoice($aSubChoiceInfo); + } + } + } + if (array_key_exists('alternatives', $aChoiceInfo['sub_options'])) { + $aSubAlternatives = $aChoiceInfo['sub_options']['alternatives']; + if (!is_null($aSubAlternatives) && is_array($aSubAlternatives)) { + foreach ($aSubAlternatives as $aSubChoiceInfo) { + $this->ProcessUnSelectedChoice($aSubChoiceInfo); + } + } + } + } + } + + private function ProcessSelectedChoice($aChoiceInfo, bool $bAllChecked) { + if (!is_array($aChoiceInfo)) { + return; + } + + $sDefault = $aChoiceInfo["default"] ?? "false"; + $sMandatory = $aChoiceInfo["mandatory"] ?? "false"; + + $aCurrentModules = $aChoiceInfo["modules"] ?? []; + if (0 === count($this->aSelectedExtensions)){ + $bSelected = $bAllChecked || $sDefault === "true" || $sMandatory === "true"; + } else { + $sExtensionCode = $aChoiceInfo["extension_code"] ?? null; + $bSelected = $sMandatory === "true" || + (null !== $sExtensionCode && in_array($sExtensionCode, $this->aSelectedExtensions)); + } + + foreach ($aCurrentModules as $sModuleId){ + if ($bSelected) { + $this->aSelectedModules[$sModuleId] = true; + } else { + $this->aUnSelectedModules[$sModuleId] = true; + } + } + + $aAlternatives = $aChoiceInfo["alternatives"] ?? null; + if (!is_null($aAlternatives) && is_array($aAlternatives)) { + foreach ($aAlternatives as $aSubChoiceInfo) { + if ($bSelected) { + $this->ProcessSelectedChoice($aSubChoiceInfo, $bAllChecked); + } else { + $this->ProcessUnSelectedChoice($aSubChoiceInfo); + } + } + } + + if (array_key_exists('sub_options', $aChoiceInfo)) { + if (array_key_exists('options', $aChoiceInfo['sub_options'])) { + $aSubOptions = $aChoiceInfo['sub_options']['options']; + if (!is_null($aSubOptions) && is_array($aSubOptions)) { + foreach ($aSubOptions as $aSubChoiceInfo) { + if ($bSelected) { + $this->ProcessSelectedChoice($aSubChoiceInfo, $bAllChecked); + } else { + $this->ProcessUnSelectedChoice($aSubChoiceInfo); + } + } + } + } + if (array_key_exists('alternatives', $aChoiceInfo['sub_options'])) { + $aSubAlternatives = $aChoiceInfo['sub_options']['alternatives']; + if (!is_null($aSubAlternatives) && is_array($aSubAlternatives)) { + foreach ($aSubAlternatives as $aSubChoiceInfo) { + if ($bSelected) { + $this->ProcessSelectedChoice($aSubChoiceInfo, false); + } else { + $this->ProcessUnSelectedChoice($aSubChoiceInfo); + } + } + } + } + } + } + + private function GetExtraDirs() : array { + $aSearchDirs = []; + + $aDirs = [ + '/datamodels/1.x', + '/datamodels/2.x', + 'data/' . $this->sTargetEnvironment . '-modules', + 'extensions', + ]; + foreach ($aDirs as $sRelativeDir){ + $sDirPath = APPROOT.$sRelativeDir; + if (is_dir($sDirPath)) + { + $aSearchDirs[] = $sDirPath; + } + } + + return $aSearchDirs; + } + + public function ProcessDefaultModules() : void { + $sProductionModuleDir = APPROOT.'data/' . $this->sTargetEnvironment . '-modules/'; + + $oProductionEnv = new RunTimeEnvironment(); + $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), $this->GetExtraDirs(), false, null); + + $this->aAutoSelectModules = []; + foreach ($aAvailableModules as $sModuleId => $aModule) { + if (($sModuleId != ROOT_MODULE)) { + if (isset($aModule['auto_select'])) { + $this->aAutoSelectModules[$sModuleId] = $aModule; + continue; + } + + if (($aModule['category'] == 'authentication') || (!$aModule['visible'])) { + $this->aSelectedModules[$sModuleId] = true; + continue; + } + + $bIsExtra = (array_key_exists('root_dir', $aModule) && (strpos($aModule['root_dir'], + $sProductionModuleDir) !== false)); // Some modules (root, datamodel) have no 'root_dir' + if ($bIsExtra) { + // Modules in data/production-modules/ are considered as mandatory and always installed + $this->aSelectedModules[$sModuleId] = true; + } + } + } + } + + public function ProcessAutoSelectModules() : void { + foreach($this->aAutoSelectModules as $sModuleId => $aModule) + { + try { + $bSelected = false; + SetupInfo::SetSelectedModules($this->aSelectedModules); + eval('$bSelected = ('.$aModule['auto_select'].');'); + if ($bSelected) + { + // Modules in data/production-modules/ are considered as mandatory and always installed + $this->aSelectedModules[$sModuleId] = true; + } + } + catch (Exception $e) { + } + } + } +} diff --git a/setup/unattended-install/README.md b/setup/unattended-install/README.md index d4ca4249b..bb1a19ebc 100644 --- a/setup/unattended-install/README.md +++ b/setup/unattended-install/README.md @@ -3,3 +3,23 @@ This script allows to install and update iTop via CLI. For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install) + + +#install-itop.sh +You can install your iTop by only using config-itop.php settings and run either + +- a non-ITIL iTop fresh installation (use itil-fresh-install.xml to have ITIL modules instead) +``` +./install-itop.sh ./xml_setup/fresh-install.xml +``` + +- a non-ITIL iTop upgrade (use itil-upgrade.xml to have ITIL modules instead) +``` +./install-itop.sh ./xml_setup/upgrade.xml +``` + +- a specific iTop installation by providing both xml setup file +in below example file provided is the one generated by iTop during last setup. +``` +./install-itop.sh ../../log/install-2024-04-03.xml +``` diff --git a/setup/unattended-install/install-itop.sh b/setup/unattended-install/install-itop.sh new file mode 100644 index 000000000..dc165c57f --- /dev/null +++ b/setup/unattended-install/install-itop.sh @@ -0,0 +1,50 @@ +#! /bin/bash + +CLI_NAME=$(basename $0) +DIR=$(dirname $0) +ITOP_DIR="$DIR/../.." + +HELP="Syntax: $CLI_NAME XML_SETUP [INSTALLATION_XML]" + +function HELP { + echo $HELP + exit 1 +} + +if [ $# -lt 1 ] +then + echo "Missing parameters passed." + HELP +fi + +if [ $# -gt 2 ] +then + echo "Too much parameters passed ($#) : $*." + HELP +fi + +XML_SETUP=$1 +if [ ! -f $XML_SETUP ] +then + echo "XML_SETUP file ($XML_SETUP) not found." + HELP +fi + +if [ $# -eq 2 ] +then + INSTALLATION_XML=$2 + if [ ! -f $INSTALLATION_XML ] + then + echo "INSTALLATION_XML file ($INSTALLATION_XML) not found." + HELP + fi +else + INSTALLATION_XML="$ITOP_DIR/datamodels/2.x/installation.xml" +fi + +echo "$CLI_NAME: Using XML_SETUP ($XML_SETUP) and INSTALLATION_XML ($INSTALLATION_XML) files during unattended itop installation." + +rm -rf $ITOP_DIR/data/.maintenance; +echo php $DIR/unattended-install.php --use_itop_config --installation_xml="$INSTALLATION_XML" --param-file="$XML_SETUP" + +php $DIR/unattended-install.php --use_itop_config --installation_xml="$INSTALLATION_XML" --param-file="$XML_SETUP" diff --git a/setup/unattended-install/unattended-install.php b/setup/unattended-install/unattended-install.php index ee0b3722c..10dc03fc0 100644 --- a/setup/unattended-install/unattended-install.php +++ b/setup/unattended-install/unattended-install.php @@ -1,17 +1,29 @@ [--installation_xml=] [--use_itop_config] + +Options: + --param-file= Path to the file (XML) to use for the unattended installation. That file (generated by the setup into log directory) must contain the following sections: + - target_env: the target environment (production, test, dev) + - database: the database settings (server, user, pwd, name, prefix) + - selected_modules: the list of modules to install + --response_file DEPRECATED: use `--param-file` instead + --installation_xml= Use an installation.xml file to compute the modules to install depending on the selected extensions listed in the param file + --use_itop_config Use the iTop configuration file to get the database settings, otherwise use the database settings from the parameters file + +Advanced options: + --check-consistency=1 Check the data model consistency after the installation (default: 0) + --clean=1 In case of a first installation, cleanup the environment before proceeding: delete the configuration file, the cache directory, the target directory, the database (default: 0) + --install=0 Set to 0 to perform a dry-run (default: 1) +EOF; + exit(-1); +} ///////////////////////////////////////////////// if (! utils::IsModeCLI()) { @@ -19,17 +31,22 @@ if (! utils::IsModeCLI()) exit(-1); } -$sParamFile = utils::ReadParam('response_file', 'null', true /* CLI allowed */, 'raw_data'); -if ($sParamFile === 'null') { - echo "No `--response_file` param specified, using default value !\n"; - $sParamFile = 'default-params.xml'; +if (in_array('--help', $argv)) { + PrintUsageAndExit(); } -$bCheckConsistency = (utils::ReadParam('check_consistency', '0', true /* CLI allowed */) == '1'); + +$sParamFile = utils::ReadParam('param-file', null, true /* CLI allowed */, 'raw_data') ?? utils::ReadParam('response_file', null, true /* CLI allowed */, 'raw_data'); +if (is_null($sParamFile)) { + echo "Missing mandatory argument `--param-file`.\n"; + PrintUsageAndExit(); +} +$bCheckConsistency = (utils::ReadParam('check-consistency', '0', true /* CLI allowed */) == '1'); if (false === file_exists($sParamFile)) { - echo "Param file `$sParamFile` doesn't exist ! Exiting..."; + echo "Param file `$sParamFile` doesn't exist! Exiting...\n"; exit(-1); } + $oParams = new XMLParameters($sParamFile); $sMode = $oParams->Get('mode'); @@ -40,8 +57,73 @@ if ($sTargetEnvironment == '') $sTargetEnvironment = 'production'; } -//unattended run based on db settings coming from response_file (XML file) -$aDBXmlSettings = $oParams->Get('database', array()); +$sXmlSetupBaseName = basename($sParamFile); +$sInstallationXmlPath = utils::ReadParam('installation_xml', null, true /* CLI allowed */, 'raw_data'); +if (! is_null($sInstallationXmlPath) && is_file($sInstallationXmlPath)) { + $sInstallationBaseName = basename($sInstallationXmlPath); + + $aSelectedExtensionsFromXmlSetup = $oParams->Get('selected_extensions', []); + if (count($aSelectedExtensionsFromXmlSetup) !== 0) { + $sMsg = "Modules to install computed based on $sInstallationBaseName file and installation choices (listed in section `selected_extensions` of $sXmlSetupBaseName file)"; + echo "$sMsg:\n".implode(',', $aSelectedExtensionsFromXmlSetup)."\n\n"; + SetupLog::Info($sMsg, null, $aSelectedExtensionsFromXmlSetup); + } else { + $sMsg = "Modules to install computed based on default installation choices inside $sInstallationBaseName (no choice specified in section `selected_extensions` of $sXmlSetupBaseName file)."; + echo "$sMsg\n\n"; + SetupLog::Info($sMsg); + } + + $oInstallationFileService = new InstallationFileService($sInstallationXmlPath, $sTargetEnvironment, $aSelectedExtensionsFromXmlSetup); + $oInstallationFileService->Init(); + $aComputedModules = $oInstallationFileService->GetSelectedModules(); + $aSelectedModules = array_keys($aComputedModules); + $oParams->Set('selected_modules', $aSelectedModules); + + $sMsg = "Modules to install computed"; +} else { + $aSelectedModules = $oParams->Get('selected_modules', []); + $sMsg = "Modules to install listed in $sXmlSetupBaseName (selected_modules section)"; +} + +sort($aSelectedModules); +echo "$sMsg:\n".implode(',', $aSelectedModules)."\n\n"; +SetupLog::Info($sMsg, null, $aSelectedModules); + +// Configuration file +$sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE; +$bUseItopConfig = in_array('--use_itop_config', $argv); +if ($bUseItopConfig && file_exists($sConfigFile)){ + //unattended run based on db settings coming from itop configuration + copy($sConfigFile, "$sConfigFile.backup"); + + $oConfig = new Config($sConfigFile); + $aDBXmlSettings = $oParams->Get('database', array()); + $aDBXmlSettings ['server'] = $oConfig->Get('db_host'); + $aDBXmlSettings ['user'] = $oConfig->Get('db_user'); + $aDBXmlSettings ['pwd'] = $oConfig->Get('db_pwd'); + $aDBXmlSettings ['name'] = $oConfig->Get('db_name'); + $aDBXmlSettings ['prefix'] = $oConfig->Get('db_subname'); + $aDBXmlSettings ['db_tls_enabled'] = $oConfig->Get('db_tls.enabled'); + //cannot be null or infinite loop triggered! + $aDBXmlSettings ['db_tls_ca'] = $oConfig->Get('db_tls.ca') ?? ""; + $oParams->Set('database', $aDBXmlSettings); + + $aFields = [ + 'url' => 'app_root_url', + 'source_dir' => 'source_dir', + 'graphviz_path' => 'graphviz_path', + ]; + foreach($aFields as $sSetupField => $sConfField){ + $oParams->Set($sSetupField, $oConfig->Get($sConfField)); + } + + $oParams->Set('mysql_bindir', $oConfig->GetModuleSetting('itop-backup', 'mysql_bindir', "")); + $oParams->Set('language', $oConfig->GetDefaultLanguage()); +} else { + //unattended run based on db settings coming from response_file (XML file) + $aDBXmlSettings = $oParams->Get('database', array()); +} + $sDBServer = $aDBXmlSettings['server']; $sDBUser = $aDBXmlSettings['user']; $sDBPwd = $aDBXmlSettings['pwd']; @@ -57,8 +139,6 @@ if ($sMode == 'install') { echo "Cleanup mode detected.\n"; - // Configuration file - $sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE; if (file_exists($sConfigFile)) { echo "Trying to delete the configuration file: '$sConfigFile'.\n"; @@ -200,7 +280,7 @@ if ($bHasErrors) $sLogMsg = "Encountered stopper issues. Aborting..."; echo "$sLogMsg\n"; SetupLog::Error($sLogMsg); - die; + exit(-1); } $bFoundIssues = false; @@ -291,11 +371,18 @@ if (!$bFoundIssues && $bCheckConsistency) } } -if (!$bFoundIssues) +if (! $bFoundIssues) { // last line: used to check the install // the only way to track issues in case of Fatal error or even parsing error! $sLogMsg = "installed!"; + + if ($bUseItopConfig && is_file("$sConfigFile.backup")) + { + echo "\nuse config file provided by backup in $sConfigFile."; + copy("$sConfigFile.backup", $sConfigFile); + } + SetupLog::Info($sLogMsg); echo "\n$sLogMsg"; exit(0); @@ -303,5 +390,5 @@ if (!$bFoundIssues) $sLogMsg = "installation failed!"; SetupLog::Error($sLogMsg); -echo "\n$sLogMsg"; +echo "\n$sLogMsg\n"; exit(-1); diff --git a/setup/unattended-install/xml_setup/fresh-install.xml b/setup/unattended-install/xml_setup/fresh-install.xml new file mode 100644 index 000000000..db1311cf5 --- /dev/null +++ b/setup/unattended-install/xml_setup/fresh-install.xml @@ -0,0 +1,41 @@ + + + install + + + datamodels/2.x/ + 2.7.0 + /var/www/html/iTop/conf/production/config-itop.php + extensions + production + + + + + + + + + + + + /usr/bin/dot + + + + + + + + + + + 1 + + + + + + + + diff --git a/setup/unattended-install/xml_setup/itil-fresh-install.xml b/setup/unattended-install/xml_setup/itil-fresh-install.xml new file mode 100644 index 000000000..e1b1e594f --- /dev/null +++ b/setup/unattended-install/xml_setup/itil-fresh-install.xml @@ -0,0 +1,53 @@ + + + install + + + datamodels/2.x/ + 2.7.0 + /var/www/html/iTop/conf/production/config-itop.php + extensions + production + + + + + + + + + + + + /usr/bin/dot + + + + + + + + + + + 1 + + + + + + + itop-config-mgmt-datacenter + itop-config-mgmt-end-user + itop-config-mgmt-storage + itop-config-mgmt-virtualization + itop-service-mgmt-enterprise + itop-ticket-mgmt-itil + itop-ticket-mgmt-itil-user-request + itop-ticket-mgmt-itil-incident + itop-ticket-mgmt-itil-enhanced-portal + itop-change-mgmt-itil + itop-config-mgmt-core + itop-kown-error-mgmt + + diff --git a/setup/unattended-install/xml_setup/itil-upgrade.xml b/setup/unattended-install/xml_setup/itil-upgrade.xml new file mode 100644 index 000000000..eb0a63208 --- /dev/null +++ b/setup/unattended-install/xml_setup/itil-upgrade.xml @@ -0,0 +1,53 @@ + + + upgrade + + + datamodels/2.x/ + 2.7.0 + /var/www/html/iTop/conf/production/config-itop.php + extensions + production + + + + + + + + + + + + /usr/bin/dot + + + + + + + + + + + 1 + + + + + + + itop-config-mgmt-datacenter + itop-config-mgmt-end-user + itop-config-mgmt-storage + itop-config-mgmt-virtualization + itop-service-mgmt-enterprise + itop-ticket-mgmt-itil + itop-ticket-mgmt-itil-user-request + itop-ticket-mgmt-itil-incident + itop-ticket-mgmt-itil-enhanced-portal + itop-change-mgmt-itil + itop-config-mgmt-core + itop-kown-error-mgmt + + diff --git a/setup/unattended-install/xml_setup/upgrade.xml b/setup/unattended-install/xml_setup/upgrade.xml new file mode 100644 index 000000000..ff0d153f1 --- /dev/null +++ b/setup/unattended-install/xml_setup/upgrade.xml @@ -0,0 +1,41 @@ + + + upgrade + + + datamodels/2.x/ + 2.7.0 + /var/www/html/iTop/conf/production/config-itop.php + extensions + production + + + + + + + + + + + + /usr/bin/dot + + + + + + + + + + + 1 + + + + + + + + \ No newline at end of file diff --git a/tests/ci_description.ini b/tests/ci_description.ini index ea76b363f..205a079de 100644 --- a/tests/ci_description.ini +++ b/tests/ci_description.ini @@ -5,7 +5,7 @@ php_version=8.2-apache db_version=5.7 [itop] -itop_setup=tests/setup_params/default-params.xml +;itop_setup=tests/setup_params/default-params.xml itop_backup=tests/backups/backup-itop.tar.gz [phpunit] diff --git a/tests/php-unit-tests/unitary-tests/setup/unattended-install/InstallationFileServiceTest.php b/tests/php-unit-tests/unitary-tests/setup/unattended-install/InstallationFileServiceTest.php new file mode 100644 index 000000000..768395ef0 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/unattended-install/InstallationFileServiceTest.php @@ -0,0 +1,315 @@ +sFolderToCleanup = null; + \ModuleDiscovery::ResetCache(); + } + + protected function tearDown(): void { + parent::tearDown(); + + $sModuleId = "itop-problem-mgmt"; + $this->RecurseMoveDir(APPROOT."data/production-modules/$sModuleId", APPROOT . "datamodels/2.x/$sModuleId"); + } + + public function GetDefaultModulesProvider() { + return [ + 'all checked' => [ true ], + 'only defaut + mandatory' => [ false ], + ]; + } + + /** + * @dataProvider GetDefaultModulesProvider + */ + public function testProcessInstallationChoices($bInstallationOptionalChoicesChecked=false) { + $sPath = realpath(dirname(__FILE__, 6)."/datamodels/2.x/installation.xml"); + $this->assertTrue(is_file($sPath)); + $oInstallationFileService = new \InstallationFileService($sPath, 'production', [], $bInstallationOptionalChoicesChecked); + $oInstallationFileService->ProcessInstallationChoices(); + $aExpectedModules = [ + "itop-config-mgmt", + "itop-attachments", + "itop-profiles-itil", + "itop-welcome-itil", + "itop-tickets", + "itop-files-information", + "combodo-db-tools", + "itop-core-update", + "itop-hub-connector", + "itop-oauth-client", + "itop-datacenter-mgmt", + "itop-endusers-devices", + "itop-storage-mgmt", + "itop-virtualization-mgmt", + "itop-service-mgmt", + "itop-request-mgmt", + "itop-portal", + "itop-portal-base", + "itop-change-mgmt", + ]; + + $aExpectedUnselectedModules = [ + 'itop-change-mgmt-itil', + 'itop-incident-mgmt-itil', + 'itop-request-mgmt-itil', + 'itop-service-mgmt-provider', + ]; + + if ($bInstallationOptionalChoicesChecked){ + $aExpectedModules []= "itop-problem-mgmt"; + $aExpectedModules []= "itop-knownerror-mgmt"; + } else { + $aExpectedUnselectedModules []= "itop-problem-mgmt"; + $aExpectedUnselectedModules []= "itop-knownerror-mgmt"; + } + + sort($aExpectedModules); + $aModules = array_keys($oInstallationFileService->GetSelectedModules()); + sort($aModules); + + $this->assertEquals($aExpectedModules, $aModules); + + $aUnselectedModules = array_keys($oInstallationFileService->GetUnSelectedModules()); + sort($aExpectedUnselectedModules); + sort($aUnselectedModules); + $this->assertEquals($aExpectedUnselectedModules, $aUnselectedModules); + } + + /** + * @dataProvider GetDefaultModulesProvider + */ + public function testGetAllSelectedModules($bInstallationOptionalChoicesChecked=false) { + $sPath = realpath(dirname(__FILE__, 6)."/datamodels/2.x/installation.xml"); + $oInstallationFileService = new \InstallationFileService($sPath, 'production', [], $bInstallationOptionalChoicesChecked); + $oInstallationFileService->Init(); + + $aSelectedModules = $oInstallationFileService->GetSelectedModules(); + $aExpectedInstallationModules = [ + "itop-config-mgmt", + "itop-attachments", + "itop-profiles-itil", + "itop-welcome-itil", + "itop-tickets", + "itop-files-information", + "combodo-db-tools", + "itop-core-update", + "itop-hub-connector", + "itop-oauth-client", + "itop-datacenter-mgmt", + "itop-endusers-devices", + "itop-storage-mgmt", + "itop-virtualization-mgmt", + "itop-service-mgmt", + "itop-request-mgmt", + "itop-portal", + "itop-portal-base", + "itop-change-mgmt", + ]; + if ($bInstallationOptionalChoicesChecked){ + $aExpectedInstallationModules []= "itop-problem-mgmt"; + $aExpectedInstallationModules []= "itop-knownerror-mgmt"; + } + + $aExpectedAuthenticationModules = [ + 'authent-cas', + 'authent-external', + 'authent-ldap', + 'authent-local', + ]; + + $aUnvisibleModules = [ + 'itop-backup', + 'itop-config', + 'itop-sla-computation', + ]; + + $aAutoSelectedModules = [ + 'itop-bridge-virtualization-storage', + ]; + + $this->checkModuleList("installation.xml choices", $aExpectedInstallationModules, $aSelectedModules); + $this->checkModuleList("authentication category", $aExpectedAuthenticationModules, $aSelectedModules); + $this->checkModuleList("unvisible", $aUnvisibleModules, $aSelectedModules); + $this->checkModuleList("auto-select", $aAutoSelectedModules, $aSelectedModules); + $this->assertEquals([], $aSelectedModules, "there should be no more modules remaining apart from below lists"); + } + + private function GetSelectedItilExtensions(bool $coreExtensionIncluded, bool $bKnownMgtIncluded) : array { + $aExtensions = [ + 'itop-config-mgmt-datacenter', + 'itop-config-mgmt-end-user', + 'itop-config-mgmt-storage', + 'itop-config-mgmt-virtualization', + 'itop-service-mgmt-enterprise', + 'itop-ticket-mgmt-itil', + 'itop-ticket-mgmt-itil-user-request', + 'itop-ticket-mgmt-itil-incident', + 'itop-ticket-mgmt-itil-enhanced-portal', + 'itop-change-mgmt-itil', + ]; + + if ($coreExtensionIncluded){ + $aExtensions[]= 'itop-config-mgmt-core'; + } + + if ($bKnownMgtIncluded){ + $aExtensions[]= 'itop-kown-error-mgmt'; + } + + return $aExtensions; + + } + + public function ItilExtensionProvider() { + return [ + 'all itil extensions + INCLUDING known-error-mgt' => [ + 'aSelectedExtensions' => $this->GetSelectedItilExtensions(true, true), + 'bKnownMgtSelected' => true, + ], + 'all itil extensions WITHOUT known-error-mgt' => [ + 'aSelectedExtensions' => $this->GetSelectedItilExtensions(true, false), + 'bKnownMgtSelected' => false, + ], + 'all itil extensions WITHOUT core mandatory ones + INCLUDING known-error-mgt' => [ + 'aSelectedExtensions' => $this->GetSelectedItilExtensions(false, true), + 'bKnownMgtSelected' => true, + ], + 'all itil extensions WITHOUT core mandatory ones and WITHOUT known-error-mgt' => [ + 'aSelectedExtensions' => $this->GetSelectedItilExtensions(false, false), + 'bKnownMgtSelected' => false, + ], + ]; + } + + /** + * @dataProvider ItilExtensionProvider + */ + public function testGetAllSelectedModules_withItilExtensions(array $aSelectedExtensions, bool $bKnownMgtSelected) { + $sPath = realpath(dirname(__FILE__, 6)."/datamodels/2.x/installation.xml"); + $oInstallationFileService = new \InstallationFileService($sPath, 'production', $aSelectedExtensions); + $oInstallationFileService->Init(); + + $aSelectedModules = $oInstallationFileService->GetSelectedModules(); + $aExpectedInstallationModules = [ + "itop-config-mgmt", + "itop-attachments", + "itop-profiles-itil", + "itop-welcome-itil", + "itop-tickets", + "itop-files-information", + "combodo-db-tools", + "itop-core-update", + "itop-hub-connector", + "itop-oauth-client", + "itop-datacenter-mgmt", + "itop-endusers-devices", + "itop-storage-mgmt", + "itop-virtualization-mgmt", + "itop-service-mgmt", + "itop-request-mgmt-itil", + "itop-incident-mgmt-itil", + "itop-portal", + "itop-portal-base", + "itop-change-mgmt-itil", + "itop-full-itil", + ]; + if ($bKnownMgtSelected){ + $aExpectedInstallationModules []= "itop-knownerror-mgmt"; + } + + $aExpectedAuthenticationModules = [ + 'authent-cas', + 'authent-external', + 'authent-ldap', + 'authent-local', + ]; + + $aUnvisibleModules = [ + 'itop-backup', + 'itop-config', + 'itop-sla-computation', + ]; + + $aAutoSelectedModules = [ + 'itop-bridge-virtualization-storage', + ]; + + $this->checkModuleList("installation.xml choices", $aExpectedInstallationModules, $aSelectedModules); + $this->checkModuleList("authentication category", $aExpectedAuthenticationModules, $aSelectedModules); + $this->checkModuleList("unvisible", $aUnvisibleModules, $aSelectedModules); + $this->checkModuleList("auto-select", $aAutoSelectedModules, $aSelectedModules); + $this->assertEquals([], $aSelectedModules, "there should be no more modules remaining apart from below lists"); + } + + private function checkModuleList(string $sModuleCategory, array $aExpectedModuleList, array &$aSelectedModules) { + $aMissingModules = []; + + foreach ($aExpectedModuleList as $sModuleId){ + if (! array_key_exists($sModuleId, $aSelectedModules)){ + $aMissingModules[]=$sModuleId; + } else { + unset($aSelectedModules[$sModuleId]); + } + } + + $this->assertEquals([], $aMissingModules, "$sModuleCategory modules are missing"); + + } + + public function ProductionModulesProvider() { + return [ + 'module autoload as located in production-modules' => [ true ], + 'module not loaded' => [ false ], + ]; + } + + /** + * @dataProvider ProductionModulesProvider + */ + public function testGetAllSelectedModules_ProductionModules(bool $bModuleInProductionModulesFolder) { + $sModuleId = "itop-problem-mgmt"; + if ($bModuleInProductionModulesFolder){ + if (! is_dir(APPROOT."data/production-modules")){ + @mkdir(APPROOT."data/production-modules"); + } + + $this->RecurseMoveDir(APPROOT . "datamodels/2.x/$sModuleId", APPROOT."data/production-modules/$sModuleId"); + } + + $sPath = realpath(dirname(__FILE__, 6)."/datamodels/2.x/installation.xml"); + $oInstallationFileService = new \InstallationFileService($sPath, 'production', [], false); + $oInstallationFileService->Init(); + + $aSelectedModules = $oInstallationFileService->GetSelectedModules(); + $this->assertEquals($bModuleInProductionModulesFolder, array_key_exists($sModuleId, $aSelectedModules)); + } + + private function RecurseMoveDir($sFromDir, $sToDir) { + if (! is_dir($sFromDir)){ + return; + } + + if (! is_dir($sToDir)){ + @mkdir($sToDir); + } + + foreach (glob("$sFromDir/*") as $sPath){ + $sToPath = $sToDir.'/'.basename($sPath); + if (is_file($sPath)){ + @rename($sPath, $sToPath); + } else { + $this->RecurseMoveDir($sPath, $sToPath); + } + } + + @rmdir($sFromDir); + } +} diff --git a/tests/php-unit-tests/unitary-tests/setup/unattended-install/UnattendedInstallTest.php b/tests/php-unit-tests/unitary-tests/setup/unattended-install/UnattendedInstallTest.php index ee4bdd1d3..cde7af01a 100644 --- a/tests/php-unit-tests/unitary-tests/setup/unattended-install/UnattendedInstallTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/unattended-install/UnattendedInstallTest.php @@ -63,9 +63,12 @@ class UnattendedInstallTest extends ItopDataTestCase } public function testCallUnattendedInstallFromCLI() { - $cliPath = realpath(APPROOT."/setup/unattended-install/unattended-install.php"); - $res = exec("php ".$cliPath); + $sCliPath = realpath(APPROOT."/setup/unattended-install/unattended-install.php"); + exec(sprintf("%s %s", PHP_BINARY, $sCliPath), $aOutput, $iCode); - $this->assertEquals("Param file `default-params.xml` doesn't exist ! Exiting...", $res); + $sOutput = implode('\n', $aOutput); + var_dump($sOutput); + $this->assertStringContainsString("Missing mandatory argument `--param-file`", $sOutput); + $this->assertEquals(255, $iCode); } } diff --git a/tests/setup_params/default-params.xml b/tests/setup_params/default-params.xml index 61b1e19af..dcaf30986 100644 --- a/tests/setup_params/default-params.xml +++ b/tests/setup_params/default-params.xml @@ -1,82 +1,41 @@ - - install - - - - datamodels/2.x/ - 2.5.0 - default-config-itop.php - extensions - production - - - - jenkins_itop - IKnowYouSeeMeInJenkinsConf - itop_ci - - - - - http://localhost/iTop/ - /usr/bin/dot - - admin - admin - EN US - - EN US - - authent-cas - authent-external - authent-ldap - authent-local - itop-backup - itop-config - itop-files-information - itop-portal-base - itop-profiles-itil - itop-sla-computation - itop-welcome-itil - itop-structure - itop-config-mgmt - itop-attachments - itop-tickets - combodo-db-tools - itop-core-update - itop-hub-connector - itop-datacenter-mgmt - itop-endusers-devices - itop-storage-mgmt - itop-virtualization-mgmt - itop-bridge-virtualization-storage - itop-service-mgmt - itop-bridge-cmdb-ticket - itop-bridge-cmdb-services - itop-request-mgmt - itop-portal - itop-change-mgmt - itop-knownerror-mgmt - itop-faq-light - - - itop-config-mgmt-core - itop-config-mgmt-datacenter - itop-config-mgmt-end-user - itop-config-mgmt-storage - itop-config-mgmt-virtualization - itop-service-mgmt-enterprise - itop-ticket-mgmt-simple-ticket - itop-ticket-mgmt-simple-ticket-enhanced-portal - itop-change-mgmt-simple - itop-kown-error-mgmt - - 1 - - - 1 - - + + install + + + + datamodels/2.x/ + 2.5.0 + default-config-itop.php + extensions + production + + + + jenkins_itop + IKnowYouSeeMeInJenkinsConf + itop_ci + + + + + http://localhost/iTop/ + /usr/bin/dot + + admin + admin + EN US + + EN US + + + + + 1 + + + 1 + +