");
+ $oPage->add('
');
+ $oPage->add("
");
$oPage->add('
');
- foreach ($aAvailableModules as $sModuleId => $aModuleData)
+
+ require_once(APPROOT.'setup/extensionsmap.class.inc.php');
+ $oExtensionsMap = new iTopExtensionsMap();
+ $oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
+ $aChoices = $oExtensionsMap->GetChoices();
+ foreach ($aChoices as $oExtension)
{
- if ($sModuleId == '_Root_') continue;
- if (!$aModuleData['visible']) continue;
- if ($aModuleData['version_db'] == '') continue;
- $oPage->add('- '.$aModuleData['label'].' ('.$aModuleData['version_db'].')
');
+ switch($oExtension->sSource)
+ {
+ case iTopExtension::SOURCE_REMOTE:
+ $sSource = ' '.Dict::S('UI:About:RemoteExtensionSource').'';
+ break;
+
+ case iTopExtension::SOURCE_MANUAL:
+ $sSource = ' '.Dict::S('UI:About:ManualExtensionSource').'';
+ break;
+
+ default:
+ $sSource = '';
+ }
+ $oPage->add('- '.$oExtension->sLabel.$sSource.'
');
}
$oPage->add('
');
$oPage->add("
");
@@ -1280,6 +1294,25 @@ EOF
$oPage->add('InstallDate: '.$sLastInstallDate."\n");
$oPage->add('InstallPath: '.APPROOT."\n");
+ $oPage->add("---- Installation choices ----\n");
+ foreach ($aChoices as $oExtension)
+ {
+ switch($oExtension->sSource)
+ {
+ case iTopExtension::SOURCE_REMOTE:
+ $sSource = ' ('.Dict::S('UI:About:RemoteExtensionSource').')';
+ break;
+
+ case iTopExtension::SOURCE_MANUAL:
+ $sSource = ' ('.Dict::S('UI:About:ManualExtensionSource').')';
+ break;
+
+ default:
+ $sSource = '';
+ }
+ $oPage->add('InstalledExtension/'.$oExtension->sCode.'/'.$oExtension->sVersion.$sSource."\n");
+ }
+ $oPage->add("---- Actual modules installed ----\n");
foreach ($aAvailableModules as $sModuleId => $aModuleData)
{
if ($sModuleId == '_Root_') continue;
diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php
index e2de6f5cb..1c3484652 100644
--- a/setup/applicationinstaller.class.inc.php
+++ b/setup/applicationinstaller.class.inc.php
@@ -198,7 +198,6 @@ class ApplicationInstaller
$sTargetEnvironment = 'production';
}
$sTargetDir = 'env-'.$sTargetEnvironment;
- $sWorkspaceDir = $this->oParams->Get('workspace_dir', 'workspace');
$bUseSymbolicLinks = false;
$aMiscOptions = $this->oParams->Get('options', array());
if (isset($aMiscOptions['symlinks']) && $aMiscOptions['symlinks'] )
@@ -299,7 +298,6 @@ class ApplicationInstaller
$sDBPwd = $aDBParams['pwd'];
$sDBName = $aDBParams['name'];
$sDBPrefix = $aDBParams['prefix'];
- $aFiles = $this->oParams->Get('files', array());
$bOldAddon = $this->oParams->Get('old_addon', false);
$bSampleData = ($this->oParams->Get('sample_data', 0) == 1);
@@ -331,13 +329,14 @@ class ApplicationInstaller
$sUrl = $this->oParams->Get('url', '');
$sGraphvizPath = $this->oParams->Get('graphviz_path', '');
$sLanguage = $this->oParams->Get('language', '');
- $aSelectedModules = $this->oParams->Get('selected_modules', array());
+ $aSelectedModuleCodes = $this->oParams->Get('selected_modules', array());
+ $aSelectedExtensionCodes = $this->oParams->Get('selected_extensions', array());
$bOldAddon = $this->oParams->Get('old_addon', false);
$sSourceDir = $this->oParams->Get('source_dir', '');
$sPreviousConfigFile = $this->oParams->Get('previous_configuration_file', '');
$sDataModelVersion = $this->oParams->Get('datamodel_version', '0.0.0');
- self::DoCreateConfig($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment, $bOldAddon, $sSourceDir, $sPreviousConfigFile, $sDataModelVersion, $sGraphvizPath);
+ self::DoCreateConfig($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sTargetEnvironment, $bOldAddon, $sSourceDir, $sPreviousConfigFile, $sDataModelVersion, $sGraphvizPath);
$aResult = array(
'status' => self::INFO,
@@ -491,7 +490,7 @@ class ApplicationInstaller
$aModules = $oFactory->FindModules();
- foreach($aModules as $foo => $oModule)
+ foreach($aModules as $oModule)
{
$sModule = $oModule->GetName();
if (in_array($sModule, $aSelectedModules))
@@ -531,8 +530,8 @@ class ApplicationInstaller
SetupPage::log_info("Data model successfully compiled to '$sTargetPath'.");
$sCacheDir = APPROOT.'/data/cache-'.$sEnvironment.'/';
- Setuputils::builddir($sCacheDir);
- Setuputils::tidydir($sCacheDir);
+ SetupUtils::builddir($sCacheDir);
+ SetupUtils::tidydir($sCacheDir);
}
// Special case to patch a ugly patch in itop-config-mgmt
@@ -548,7 +547,7 @@ class ApplicationInstaller
// 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');
+ SetupUtils::builddir(APPROOT.'data');
if (!file_exists($sInstanceUUIDFile))
{
$sIntanceUUID = utils::CreateUUID('filesystem');
@@ -690,7 +689,8 @@ class ApplicationInstaller
// 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();
- foreach(Dict::GetLanguages() as $sLangCode => $aLang)
+ $aSuffixes = array();
+ foreach(array_keys(Dict::GetLanguages()) as $sLangCode)
{
Dict::SetUserLanguage($sLangCode);
$sSuffix = CMDBSource::Quote('%'.Dict::S('Core:SyncDataExchangeComment'));
@@ -966,7 +966,7 @@ class ApplicationInstaller
SetupPage::log_info("ending data load session");
}
- protected static function DoCreateConfig($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment, $bOldAddon, $sSourceDir, $sPreviousConfigFile, $sDataModelVersion, $sGraphvizPath)
+ protected static function DoCreateConfig($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sTargetEnvironment, $bOldAddon, $sSourceDir, $sPreviousConfigFile, $sDataModelVersion, $sGraphvizPath)
{
$aParamValues = array(
'mode' => $sMode,
@@ -979,7 +979,7 @@ class ApplicationInstaller
'application_path' => $sUrl,
'language' => $sLanguage,
'graphviz_path' => $sGraphvizPath,
- 'selected_modules' => implode(',', $aSelectedModules),
+ 'selected_modules' => implode(',', $aSelectedModuleCodes)
);
$bPreserveModuleSettings = false;
@@ -1024,8 +1024,7 @@ class ApplicationInstaller
// Record which modules are installed...
$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment);
$oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database
- $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir);
- if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModules, $sModulesDir))
+ if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sModulesDir))
{
throw new Exception("Failed to record the installation information");
}
diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php
new file mode 100644
index 000000000..64b951f33
--- /dev/null
+++ b/setup/extensionsmap.class.inc.php
@@ -0,0 +1,458 @@
+sCode = '';
+ $this->sLabel = '';
+ $this->sDescription = '';
+ $this->sSource = self::SOURCE_WIZARD;
+ $this->bMandatory = false;
+ $this->sMoreInfoUrl = '';
+ $this->bMarkedAsChosen = false;
+ $this->sVersion = ITOP_VERSION;
+ $this->sInstalledVersion = '';
+ }
+}
+
+/**
+ * Helper class to discover all available extensions on a given iTop system
+ */
+class iTopExtensionsMap
+{
+ /**
+ * The list of all discovered extensions
+ * @var iTopExtension[]
+ */
+ protected $aExtensions;
+
+ public function __construct($sFromEnvironment = 'production')
+ {
+ $this->aExtensions = array();
+ $this->ScanDisk($sFromEnvironment);
+ }
+
+ /**
+ * Populate the list of available (pseudo)extensions by scanning the disk
+ * where the iTop files are located
+ * @param string $sEnvironment
+ * @return void
+ */
+ protected function ScanDisk($sEnvironment)
+ {
+ if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x') && !$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x'))
+ {
+ if(!$this->ReadDir(APPROOT.'/datamodels/2.x', iTopExtension::SOURCE_WIZARD)) $this->ReadDir(APPROOT.'/datamodels/1.x', iTopExtension::SOURCE_WIZARD);
+ }
+ $this->ReadDir(APPROOT.'/extensions', iTopExtension::SOURCE_MANUAL);
+ $this->ReadDir(APPROOT.'/data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE);
+ }
+
+ /**
+ * Read the information contained in the "installation.xml" file in the given directory
+ * and create pseudo extensions from the list of choices described in this file
+ * @param string $sDir
+ * @return boolean Return true if the installation.xml file exists and is readable
+ */
+ protected function ReadInstallationWizard($sDir)
+ {
+ if (!is_readable($sDir.'/installation.xml')) return false;
+
+ $oXml = new XMLParameters($sDir.'/installation.xml');
+ foreach($oXml->Get('steps') as $aStepInfo)
+ {
+ if (array_key_exists('options', $aStepInfo))
+ {
+ $this->ProcessWizardChoices($aStepInfo['options']);
+ }
+ if (array_key_exists('alternatives', $aStepInfo))
+ {
+ $this->ProcessWizardChoices($aStepInfo['alternatives']);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Helper to process a "choice" array read from the installation.xml file
+ * @param array $aChoices
+ * @return void
+ */
+ protected function ProcessWizardChoices($aChoices)
+ {
+ foreach($aChoices as $aChoiceInfo)
+ {
+ if (array_key_exists('extension_code', $aChoiceInfo))
+ {
+ $oExtension = new iTopExtension();
+ $oExtension->sCode = $aChoiceInfo['extension_code'];
+ $oExtension->sLabel = $aChoiceInfo['title'];
+ if (array_key_exists('modules', $aChoiceInfo))
+ {
+ // Some wizard choices are not associated with any module
+ $oExtension->aModules = $aChoiceInfo['modules'];
+ }
+ if (array_key_exists('sub_options', $aChoiceInfo))
+ {
+ if (array_key_exists('options', $aChoiceInfo['sub_options']))
+ {
+ $this->ProcessWizardChoices($aChoiceInfo['sub_options']['options']);
+ }
+ if (array_key_exists('alternatives', $aChoiceInfo['sub_options']))
+ {
+ $this->ProcessWizardChoices($aChoiceInfo['sub_options']['alternatives']);
+ }
+ }
+ $this->AddExtension($oExtension);
+ }
+ }
+ }
+
+ /**
+ * Add an extension to the list of existing extensions, taking care of removing duplicates
+ * (only the latest/greatest version is kept)
+ * @param iTopExtension $oNewExtension
+ * @return void
+ */
+ protected function AddExtension(iTopExtension $oNewExtension)
+ {
+ foreach($this->aExtensions as $key => $oExtension)
+ {
+ if ($oExtension->sCode == $oNewExtension->sCode)
+ {
+ if (version_compare($oNewExtension->sVersion, $oExtension->sVersion, '>'))
+ {
+ // This "new" extension is "newer" than the previous one, let's replace the previous one
+ unset($this->aExtensions[$key]);
+ $this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
+ return;
+ }
+ else
+ {
+ // This "new" extension is not "newer" than the previous one, let's ignore it
+ return;
+ }
+ }
+ }
+ // Finally it's not a duplicate, let's add it to the list
+ $this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
+ }
+
+ /**
+ * Read (recursively) a directory to find if it contains extensions (or modules)
+ * @param string $sSearchDir The directory to scan
+ * @param string $sSource The 'source' value for the extensions found in this directory
+ * @param string|null $sParentExtensionId Not null if the directory is under a declared extension
+ * @return boolean
+ */
+ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null)
+ {
+ if (!is_readable($sSearchDir)) return false;
+ $hDir = opendir($sSearchDir);
+ if ($hDir !== false)
+ {
+ $sExtensionId = null;
+ $aSubDirectories = array();
+
+ // First check if there is an extension.xml file in this directory
+ if (is_readable($sSearchDir.'/extension.xml'))
+ {
+ $oXml = new XMLParameters($sSearchDir.'/extension.xml');
+ $oExtension = new iTopExtension();
+ $oExtension->sCode = $oXml->Get('extension_code');
+ $oExtension->sLabel = $oXml->Get('label');
+ $oExtension->sDescription = $oXml->Get('description');
+ $oExtension->sVersion = $oXml->Get('version');
+ $oExtension->bMandatory = ($oXml->Get('mandatory') == 'true');
+ $oExtension->sMoreInfoUrl = $oXml->Get('more_info_url');
+ $oExtension->sVersion = $oXml->Get('version');
+ $oExtension->sSource = $sSource;
+
+ $sParentExtensionId = $sExtensionId = $oExtension->sCode.'/'.$oExtension->sVersion;
+ $this->AddExtension($oExtension);
+ }
+ // Then scan the other files and subdirectories
+ while (($sFile = readdir($hDir)) !== false)
+ {
+ if (($sFile !== '.') && ($sFile !== '..'))
+ {
+ $aMatches = array();
+ if (is_dir($sSearchDir.'/'.$sFile))
+ {
+ // Recurse after parsing all the regular files
+ $aSubDirectories[] = $sSearchDir.'/'.$sFile;
+ }
+ else if (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches))
+ {
+ // Found a module
+ $aModuleInfo = $this->GetModuleInfo($sSearchDir.'/'.$sFile);
+ // If we are not already inside a formal extension, then the module itself is considered
+ // as an extension, otherwise, the module is just added to the list of modules belonging
+ // to this extension
+ $sModuleId = $aModuleInfo[1];
+ list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
+
+ if ($sParentExtensionId !== null)
+ {
+ // Already inside an extension, let's add this module the list of modules belonging to this extension
+ $this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName;
+ }
+ else
+ {
+ // Not already inside an folder containing an 'extension.xml' file
+
+ // Ignore non-visible modules and auto-select ones, since these are never prompted
+ // as a choice to the end-user
+ if (!$aModuleInfo[2]['visible'] || isset($aModuleInfo[2]['auto_select'])) continue;
+
+ // Let's create a "fake" extension from this module (containing just this module) for backwards compatibility
+ $sExtensionId = $sModuleId;
+
+ $oExtension = new iTopExtension();
+ $oExtension->sCode = $sModuleName;
+ $oExtension->sLabel = $aModuleInfo[2]['label'];
+ $oExtension->sDescription = '';
+ $oExtension->sVersion = $sModuleVersion;
+ $oExtension->sSource = $sSource;
+ $oExtension->bMandatory = $aModuleInfo[2]['mandatory'];
+ $oExtension->sMoreInfoUrl = $aModuleInfo[2]['doc.more_information'];
+ $oExtension->aModules = array($sModuleName);
+
+ $this->AddExtension($oExtension);
+
+ }
+ }
+ }
+ }
+ closedir($hDir);
+ foreach($aSubDirectories as $sDir)
+ {
+ // Recurse inside the subdirectories
+ $this->ReadDir($sDir, $sSource, $sExtensionId);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Read the information from a module file (module.xxx.php)
+ * Closely inspired (almost copied/pasted !!) from ModuleDiscovery::ListModuleFiles
+ * @param string $sModuleFile
+ * @return array
+ */
+ protected function GetModuleInfo($sModuleFile)
+ {
+ static $iDummyClassIndex = 0;
+
+ $aModuleInfo = array(); // will be filled by the "eval" line below...
+ try
+ {
+ $aMatches = array();
+ $sModuleFileContents = file_get_contents($sModuleFile);
+ $sModuleFileContents = str_replace(array(''), '', $sModuleFileContents);
+ $sModuleFileContents = str_replace('__FILE__', "'".addslashes($sModuleFile)."'", $sModuleFileContents);
+ preg_match_all('/class ([A-Za-z0-9_]+) extends ([A-Za-z0-9_]+)/', $sModuleFileContents, $aMatches);
+ //print_r($aMatches);
+ $idx = 0;
+ foreach($aMatches[1] as $sClassName)
+ {
+ if (class_exists($sClassName))
+ {
+ // rename any class declaration inside the code to prevent a "duplicate class" declaration
+ // and change its parent class as well so that nobody will find it and try to execute it
+ $sModuleFileContents = str_replace($sClassName.' extends '.$aMatches[2][$idx], $sClassName.'_'.($iDummyClassIndex++).' extends DummyHandler', $sModuleFileContents);
+ }
+ $idx++;
+ }
+ // Replace the main function call by an assignment to a variable, as an array...
+ $sModuleFileContents = str_replace(array('SetupWebPage::AddModule', 'ModuleDiscovery::AddModule'), '$aModuleInfo = array', $sModuleFileContents);
+
+ eval($sModuleFileContents); // Assigns $aModuleInfo
+
+ if (count($aModuleInfo) === 0)
+ {
+ SetupPage::log_warning("Eval of $sModuleFile did not return the expected information...");
+ }
+ }
+ catch(Exception $e)
+ {
+ // Continue...
+ SetupPage::log_warning("Eval of $sModuleFile caused an exception: ".$e->getMessage());
+ }
+ return $aModuleInfo;
+ }
+
+ /**
+ * Get all available extensions
+ * @return iTopExtension[]
+ */
+ public function GetAllExtensions()
+ {
+ return $this->aExtensions;
+ }
+
+ /**
+ * Mark the given extension as chosen
+ * @param string $sExtensionCode The code of the extension (code without verison number)
+ * @param bool $bMark The value to set for the bmarkAschosen flag
+ * @return void
+ */
+ public function MarkAsChosen($sExtensionCode, $bMark = true)
+ {
+ foreach($this->aExtensions as $oExtension)
+ {
+ if ($oExtension->sCode == $sExtensionCode)
+ {
+ $oExtension->bMarkedAsChosen = $bMark;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Tells if a given extension(code) is marked as chosen
+ * @param string $sExtensionCode
+ * @return boolean
+ */
+ public function IsMarkedAsChosen($sExtensionCode)
+ {
+ foreach($this->aExtensions as $oExtension)
+ {
+ if ($oExtension->sCode == $sExtensionCode)
+ {
+ return $oExtension->bMarkedAsChosen;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Set the 'installed_version' of the given extension(code)
+ * @param string $sExtensionCode
+ * @param string $sInstalledVersion
+ * @return void
+ */
+ protected function SetInstalledVersion($sExtensionCode, $sInstalledVersion)
+ {
+ foreach($this->aExtensions as $oExtension)
+ {
+ if ($oExtension->sCode == $sExtensionCode)
+ {
+ $oExtension->sInstalledVersion = $sInstalledVersion;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Get the list of the "chosen" extensions
+ * @return iTopExtension[]
+ */
+ public function GetChoices()
+ {
+ $aResult = array();
+ foreach($this->aExtensions as $oExtension)
+ {
+ if ($oExtension->bMarkedAsChosen)
+ {
+ $aResult[] = $oExtension;
+ }
+ }
+ return $aResult;
+ }
+
+ /**
+ * Load the choices (i.e. MarkedAsChosen) from the database defined in the supplied Config
+ * @param Config $oConfig
+ * @return bool
+ */
+ public function LoadChoicesFromDatabase(Config $oConfig)
+ {
+ $aInstalledExtensions = array();
+ try
+ {
+ if (CMDBSource::DBName() === null)
+ {
+ CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName());
+ CMDBSource::SetCharacterSet($oConfig->GetDBCharacterSet(), $oConfig->GetDBCollation());
+ }
+ $sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->GetDBSubname()."priv_extension_install");
+ $aInstalledExtensions = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->GetDBSubname()."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
+ }
+ catch (MySQLException $e)
+ {
+ // No database or erroneous information
+ $aInstalledExtensions = array();
+ return false;
+ }
+
+ foreach($aInstalledExtensions as $aDBInfo)
+ {
+ $this->MarkAsChosen($aDBInfo['code']);
+ $this->SetInstalledVersion($aDBInfo['code'], $aDBInfo['version']);
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php
index 78acc5e2b..d3bf56f5c 100644
--- a/setup/modelfactory.class.inc.php
+++ b/setup/modelfactory.class.inc.php
@@ -220,13 +220,19 @@ class MFModule
public function SetFilesToInclude($aFiles, $sCategory)
{
+ // Now ModuleDiscovery provides us directly with relative paths... nothing to do
+ $this->aFilesToInclude[$sCategory] = $aFiles;
+
+ /*
$sDir = basename($this->sRootDir);
$iLen = strlen($sDir.'/');
foreach($aFiles as $sFile)
{
$iPos = strpos($sFile, $sDir.'/');
- $this->aFilesToInclude[$sCategory][] = substr($sFile, $iPos+$iLen);
+ //$this->aFilesToInclude[$sCategory][] = substr($sFile, $iPos+$iLen);
+ $this->aFilesToInclude[$sCategory][] = $sFile;
}
+ */
}
public function GetFilesToInclude($sCategory)
diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php
index 490b14e6a..edf61ae56 100644
--- a/setup/modulediscovery.class.inc.php
+++ b/setup/modulediscovery.class.inc.php
@@ -67,7 +67,7 @@ class ModuleDiscovery
// Assume 1.0.2
$aArgs['itop_version'] = '1.0.2';
}
- foreach (self::$m_aModuleArgs as $sArgName => $sArgDesc)
+ foreach (array_keys(self::$m_aModuleArgs) as $sArgName)
{
if (!array_key_exists($sArgName, $aArgs))
{
@@ -110,6 +110,8 @@ class ModuleDiscovery
self::$m_aModules[$sId] = $aArgs;
+ // Now keep the relative paths, as provided
+ /*
foreach(self::$m_aFilesList as $sAttribute)
{
if (isset(self::$m_aModules[$sId][$sAttribute]))
@@ -122,7 +124,9 @@ class ModuleDiscovery
}
}
}
+ */
// Populate automatically the list of dictionary files
+ $aMatches = array();
if(preg_match('|^([^/]+)|', $sId, $aMatches)) // ModuleName = everything before the first forward slash
{
$sModuleName = $aMatches[1];
@@ -240,6 +244,7 @@ class ModuleDiscovery
// Separate the module names from their version for an easier comparison later
foreach($aOrderedModules as $sModuleId)
{
+ $aMatches = array();
if (preg_match('|^([^/]+)/(.*)$|', $sModuleId, $aMatches))
{
$aModuleVersions[$aMatches[1]] = $aMatches[2];
@@ -260,6 +265,7 @@ class ModuleDiscovery
{
// $sModuleId in the dependency string is made of a
/
// where the operator is < <= = > >= (by default >=)
+ $aModuleMatches = array();
if(preg_match('|^([^/]+)/(>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches))
{
$sModuleName = $aModuleMatches[1];
@@ -295,7 +301,7 @@ class ModuleDiscovery
}
}
$bMissingPrerequisite = false;
- foreach ($aPotentialPrerequisites as $sModuleName => $void)
+ foreach (array_keys($aPotentialPrerequisites) as $sModuleName)
{
if (array_key_exists($sModuleName, $aSelectedModules))
{
@@ -378,6 +384,7 @@ class ModuleDiscovery
*/
public static function GetModuleName($sModuleId)
{
+ $aMatches = array();
if (preg_match('!^(.*)/(.*)$!', $sModuleId, $aMatches))
{
$sName = $aMatches[1];
@@ -394,12 +401,12 @@ class ModuleDiscovery
/**
* Helper function to browse a directory and get the modules
* @param $sRelDir string Directory to start from
+ * @param $sRootDir string The root directory path
* @return array(name, version)
*/
protected static function ListModuleFiles($sRelDir, $sRootDir)
{
static $iDummyClassIndex = 0;
- static $aDefinedClasses = array();
$sDirectory = $sRootDir.'/'.$sRelDir;
if ($hDir = opendir($sDirectory))
@@ -504,3 +511,4 @@ class SetupWebPage extends ModuleDiscovery
*/
class DummyHandler {
}
+
diff --git a/setup/moduleinstallation.class.inc.php b/setup/moduleinstallation.class.inc.php
index 8b287810f..fc5d2bb27 100644
--- a/setup/moduleinstallation.class.inc.php
+++ b/setup/moduleinstallation.class.inc.php
@@ -1,5 +1,5 @@
+/**
+ * Persistent class ExtensionInstallation to record the installed extensions
+ * Log of extensions installations
+ *
+ * @copyright Copyright (C) 2017 Combodo SARL
+ * @license http://opensource.org/licenses/AGPL-3.0
+ */
+
+class ExtensionInstallation extends cmdbAbstractObject
+{
+ public static function Init()
+ {
+ $aParams = array
+ (
+ "category" => "core,view_in_gui",
+ "key_type" => "autoincrement",
+ "name_attcode" => "",
+ "state_attcode" => "",
+ "reconc_keys" => array(),
+ "db_table" => "priv_extension_install",
+ "db_key_field" => "id",
+ "db_finalclass_field" => "",
+ "display_template" => "",
+ );
+ MetaModel::Init_Params($aParams);
+ //MetaModel::Init_InheritAttributes();
+ MetaModel::Init_AddAttribute(new AttributeString("code", array("allowed_values"=>null, "sql"=>"code", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeString("label", array("allowed_values"=>null, "sql"=>"label", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeString("source", array("allowed_values"=>null, "sql"=>"source", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values"=>null, "sql"=>"installed", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+
+ // Display lists
+ MetaModel::Init_SetZListItems('details', array('code', 'label', 'version', 'installed', 'source')); // Attributes to be displayed for the complete details
+ MetaModel::Init_SetZListItems('list', array('code', 'label', 'version', 'installed', 'source')); // Attributes to be displayed for a list
+ MetaModel::Init_SetZListItems('standard_search', array('code', 'label', 'version', 'installed', 'source')); // Attributes to be displayed in the search form
+ }
+}
+
+
diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php
index d4b126c4a..c741625d6 100644
--- a/setup/runtimeenv.class.inc.php
+++ b/setup/runtimeenv.class.inc.php
@@ -27,6 +27,7 @@
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);
@@ -41,9 +42,16 @@ class RunTimeEnvironment
{
protected $sTargetEnv;
+ /**
+ * Extensions map of the source environment
+ * @var iTopExtensionsMap
+ */
+ protected $oExtensionsMap;
+
public function __construct($sEnvironment = 'production')
{
$this->sTargetEnv = $sEnvironment;
+ $this->oExtensionsMap = null;
}
/**
@@ -104,6 +112,11 @@ class RunTimeEnvironment
}
MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sTargetEnv);
+
+ if ($this->oExtensionsMap === null)
+ {
+ $this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
+ }
}
/**
@@ -333,11 +346,26 @@ class RunTimeEnvironment
$aRet = array();
- // Determine the installed modules
+ // 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
//
@@ -359,7 +387,7 @@ class RunTimeEnvironment
}
$aModules = $oFactory->FindModules();
- foreach($aModules as $foo => $oModule)
+ foreach($aModules as $oModule)
{
$sModule = $oModule->GetName();
$sModuleRootDir = $oModule->GetRootDir();
@@ -378,7 +406,7 @@ class RunTimeEnvironment
{
// Loop while new modules are added...
$bModuleAdded = false;
- foreach($aModules as $foo => $oModule)
+ foreach($aModules as $oModule)
{
if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect())
{
@@ -437,7 +465,6 @@ class RunTimeEnvironment
// 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');
}
- $sModule = $oModule->GetName();
$oFactory->LoadModule($oModule);
if ($oFactory->HasLoadErrors())
{
@@ -475,8 +502,8 @@ class RunTimeEnvironment
$oMFCompiler->Compile($sTargetDir, null, $bUseSymLinks);
$sCacheDir = APPROOT.'data/cache-'.$this->sTargetEnv;
- Setuputils::builddir($sCacheDir);
- Setuputils::tidydir($sCacheDir);
+ SetupUtils::builddir($sCacheDir);
+ SetupUtils::tidydir($sCacheDir);
require_once(APPROOT.'/core/dict.class.inc.php');
MetaModel::ResetCache(md5(APPROOT).'-'.$this->sTargetEnv);
@@ -627,12 +654,19 @@ class RunTimeEnvironment
$oConfig->Set('access_mode', $iPrevAccessMode);
}
- public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModules, $sModulesRelativePath, $sShortComment = null)
+ public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sModulesRelativePath, $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';
@@ -662,10 +696,11 @@ class RunTimeEnvironment
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
- // Record installed modules
+ // Record installed modules and extensions
//
+ $aAvailableExtensions = array();
$aAvailableModules = $this->AnalyzeInstallation($oConfig, APPROOT.$sModulesRelativePath);
- foreach($aSelectedModules as $sModuleId)
+ foreach($aSelectedModuleCodes as $sModuleId)
{
$aModuleData = $aAvailableModules[$sModuleId];
$sName = $sModuleId;
@@ -702,6 +737,30 @@ class RunTimeEnvironment
$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);
diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php
index b15984921..63e17383b 100644
--- a/setup/wizardsteps.class.inc.php
+++ b/setup/wizardsteps.class.inc.php
@@ -27,6 +27,7 @@ require_once(APPROOT.'setup/parameters.class.inc.php');
require_once(APPROOT.'setup/applicationinstaller.class.inc.php');
require_once(APPROOT.'setup/parameters.class.inc.php');
require_once(APPROOT.'core/mutex.class.inc.php');
+require_once(APPROOT.'setup/extensionsmap.class.inc.php');
/**
* First step of the iTop Installation Wizard: Welcome screen
@@ -1173,6 +1174,42 @@ class WizStepModulesChoice extends WizardStep
{
static protected $SEP = '_';
protected $bUpgrade = false;
+
+ /**
+ *
+ * @var iTopExtensionsMap
+ */
+ protected $oExtensionsMap;
+
+ /**
+ * Whether we were able to load the choices from the database or not
+ * @var bool
+ */
+ protected $bChoicesFromDatabase;
+
+ public function __construct(WizardController $oWizard, $sCurrentState)
+ {
+ parent::__construct($oWizard, $sCurrentState);
+ $this->bChoicesFromDatabase = false;
+ $this->oExtensionsMap = new iTopExtensionsMap();
+ $sPreviousSourceDir = $this->oWizard->GetParameter('previous_version_dir', '');
+ $sConfigPath = null;
+ if (($sPreviousSourceDir !== '') && is_readable($sPreviousSourceDir.'/conf/production/config-itop.php'))
+ {
+ $sConfigPath = $sPreviousSourceDir.'/conf/production/config-itop.php';
+ }
+ else if (is_readable(utils::GetConfigFilePath('production')))
+ {
+ $sConfigPath = utils::GetConfigFilePath('production');
+ }
+ if ($sConfigPath !== null)
+ {
+ $oConfig = new Config($sConfigPath);
+ $this->bChoicesFromDatabase = $this->oExtensionsMap->LoadChoicesFromDatabase($oConfig);
+ }
+ //echo 'Default: '.($this->bChoicesFromDatabase ? 'DB' : 'Guess').'
';
+ }
+
public function GetTitle()
{
$aStepInfo = $this->GetStepInfo();
@@ -1210,11 +1247,12 @@ class WizStepModulesChoice extends WizardStep
{
// Exiting this step of the wizard, let's convert the selection into a list of modules
$aModules = array();
+ $aExtensions = array();
$sDisplayChoices = '';
for($i = 0; $i <= $index; $i++)
{
$aStepInfo = $this->GetStepInfo($i);
- $sDisplayChoices .= $this->GetSelectedModules($aStepInfo, $aSelectedChoices[$i], $aModules);
+ $sDisplayChoices .= $this->GetSelectedModules($aStepInfo, $aSelectedChoices[$i], $aModules, '', '', $aExtensions);
}
$sDisplayChoices .= '
';
if (class_exists('CreateITILProfilesInstaller'))
@@ -1222,6 +1260,7 @@ class WizStepModulesChoice extends WizardStep
$this->oWizard->SetParameter('old_addon', true);
}
$this->oWizard->SetParameter('selected_modules', json_encode(array_keys($aModules)));
+ $this->oWizard->SetParameter('selected_extensions', json_encode($aExtensions));
$this->oWizard->SetParameter('display_choices', $sDisplayChoices);
return array('class' => 'WizStepSummary', 'state' => '');
}
@@ -1277,7 +1316,7 @@ class WizStepModulesChoice extends WizardStep
// Build the default choices
$aDefaults = array();
$aModules = SetupUtils::AnalyzeInstallation($this->oWizard);
- $this->GetDefaults($aStepInfo, $aDefaults, $aModules);
+ $aDefaults = $this->GetDefaults($aStepInfo, $aModules);
//echo "aStepInfo:\n ".print_r($aStepInfo, true)."
";
//echo "aDefaults:\n ".print_r($aDefaults, true)."
";
@@ -1351,8 +1390,82 @@ EOF
EOF
);
}
+
+ protected function GetDefaults($aInfo, $aModules, $sParentId = '')
+ {
+ $aDefaults = array();
+ if (!$this->bChoicesFromDatabase)
+ {
+ $this->GuessDefaultsFromModules($aInfo, $aDefaults, $aModules, $sParentId);
+ }
+ else
+ {
+ $this->GetDefaultsFromDatabase($aInfo, $aDefaults, $sParentId);
+ }
+ return $aDefaults;
+ }
+
+ protected function GetDefaultsFromDatabase($aInfo, &$aDefaults, $sParentId)
+ {
+ $aOptions = isset($aInfo['options']) ? $aInfo['options'] : array();
+ foreach($aOptions as $index => $aChoice)
+ {
+ $sChoiceId = $sParentId.self::$SEP.$index;
+ if ($this->bUpgrade)
+ {
+ if ($this->oExtensionsMap->IsMarkedAsChosen($aChoice['extension_code']))
+ {
+ $aDefaults[$sChoiceId] = $sChoiceId;
+ }
+ }
+ else if (isset($aChoice['default']) && $aChoice['default'])
+ {
+ $aDefaults[$sChoiceId] = $sChoiceId;
+ }
+ // Recurse for sub_options (if any)
+ if (isset($aChoice['sub_options']))
+ {
+ $this->GetDefaultsFromDatabase($aChoice['sub_options'], $aDefaults, $sChoiceId);
+ }
+ }
- protected function GetDefaults($aInfo, &$aDefaults, $aModules, $sParentId = '')
+ $aAlternatives = isset($aInfo['alternatives']) ? $aInfo['alternatives'] : array();
+ $sChoiceName = null;
+ foreach($aAlternatives as $index => $aChoice)
+ {
+ $sChoiceId = $sParentId.self::$SEP.$index;
+ if ($sChoiceName == null)
+ {
+ $sChoiceName = $sChoiceId; // All radios share the same name
+ }
+ if ($this->bUpgrade)
+ {
+ if ($this->oExtensionsMap->IsMarkedAsChosen($aChoice['extension_code']))
+ {
+ $aDefaults[$sChoiceName] = $sChoiceId;
+ }
+ }
+ else if (isset($aChoice['default']) && $aChoice['default'])
+ {
+ $aDefaults[$sChoiceName] = $sChoiceId;
+ }
+ // Recurse for sub_options (if any)
+ if (isset($aChoice['sub_options']))
+ {
+ $this->GetDefaultsFromDatabase($aChoice['sub_options'], $aDefaults, $sChoiceId);
+ }
+ }
+ }
+
+ /**
+ * Try to guess the user choices based on the current list of installed modules...
+ * @param array $aInfo
+ * @param array $aDefaults
+ * @param array $aModules
+ * @param string $sParentId
+ * @return array
+ */
+ protected function GuessDefaultsFromModules($aInfo, &$aDefaults, $aModules, $sParentId = '')
{
$aRetScore = array();
$aScores = array();
@@ -1387,7 +1500,7 @@ EOF
if (isset($aChoice['sub_options']))
{
- $aScores[$sChoiceId] = array_merge($aScores[$sChoiceId], $this->GetDefaults($aChoice['sub_options'], $aDefaults, $sChoiceId));
+ $aScores[$sChoiceId] = array_merge($aScores[$sChoiceId], $this->GuessDefaultsFromModules($aChoice['sub_options'], $aDefaults, $sChoiceId));
}
$index++;
}
@@ -1409,7 +1522,11 @@ EOF
}
if (isset($aChoice['sub_options']))
{
- $aScores[$sChoiceId] = $this->GetDefaults($aChoice['sub_options'], $aDefaults, $aModules, $sChoiceId);
+ // By default (i.e. install-mode), sub options can only be checked if the parent option is checked by default
+ if ($this->bUpgrade || (isset($aChoice['default']) && $aChoice['default']))
+ {
+ $aScores[$sChoiceId] = $this->GuessDefaultsFromModules($aChoice['sub_options'], $aDefaults, $aModules, $sChoiceId);
+ }
}
$index++;
}
@@ -1474,9 +1591,9 @@ EOF
* @param hash $aSelectedChoices List of selected choices array('name' => 'selected_value_id')
* @param hash $aModules Return parameter: List of selected modules array('module_id' => true)
* @param string $sParentId Used for recursion
- * @return void
+ * @return string A text representation of what will be installed
*/
- protected function GetSelectedModules($aInfo, $aSelectedChoices, &$aModules, $sParentId = '', $sDisplayChoices = '')
+ protected function GetSelectedModules($aInfo, $aSelectedChoices, &$aModules, $sParentId = '', $sDisplayChoices = '', &$aSelectedExtensions = null)
{
if ($sParentId == '')
{
@@ -1508,11 +1625,16 @@ EOF
$aModules[$sModuleId] = true; // store the Id of the selected module
}
}
+ $sChoiceType = isset($aChoice['type']) ? $aChoice['type'] : 'wizard_option';
+ if ($aSelectedExtensions !== null)
+ {
+ $aSelectedExtensions[] = $aChoice['extension_code'];
+ }
// Recurse only for selected choices
if (isset($aChoice['sub_options']))
{
$sDisplayChoices .= '';
- $sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices);
+ $sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices, $aSelectedExtensions);
$sDisplayChoices .= '
';
}
$sDisplayChoices .= '';
@@ -1533,6 +1655,10 @@ EOF
(isset($aSelectedChoices[$sChoiceName]) && ($aSelectedChoices[$sChoiceName] == $sChoiceId)) )
{
$sDisplayChoices .= ''.$aChoice['title'].'';
+ if ($aSelectedExtensions !== null)
+ {
+ $aSelectedExtensions[] = $aChoice['extension_code'];
+ }
if (isset($aChoice['modules']))
{
foreach($aChoice['modules'] as $sModuleId)
@@ -1544,7 +1670,7 @@ EOF
if (isset($aChoice['sub_options']))
{
$sDisplayChoices .= '';
- $sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices);
+ $sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices, $aSelectedExtensions);
$sDisplayChoices .= '
';
}
$sDisplayChoices .= '';
@@ -1619,79 +1745,68 @@ EOF
$aSteps = array();
if (@file_exists($this->GetSourceFilePath()))
{
+ // Found an "installation.xml" file, let's us tis definition for the wizard
$aParams = new XMLParameters($this->GetSourceFilePath());
$aSteps = $aParams->Get('steps', array());
- $bAddExtensionsOnly = true;
- }
- else
- {
- // No wizard configuration provided, build a standard one:
- $bAddExtensionsOnly = false;
- $aSteps[] = array(
- 'title' => 'Modules Selection',
- 'description' => 'Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.
',
- 'banner' => '/images/modules.png',
- 'options' => array()
- );
- }
-
- // Additional step for the extensions
- $aSteps[] = array(
- 'title' => 'Extensions',
- 'description' => 'Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.
',
- 'banner' => '/images/extension.png',
- 'options' => array()
+
+ // Additional step for the "extensions"
+ $aStepDefinition = array(
+ 'title' => 'Extensions',
+ 'description' => 'Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.
',
+ 'banner' => '/images/extension.png',
+ 'options' => array()
);
-
- try
- {
- $sDefaultAppPath = utils::GetDefaultUrlAppRoot();
- }
- catch(Exception $e)
- {
- $sDefaultAppPath = '..';
- }
-
- $aAvailableModules = SetupUtils::AnalyzeInstallation($this->oWizard);
- foreach($aAvailableModules as $sModuleId => $aModule)
- {
- if ($sModuleId == ROOT_MODULE) continue; // Convention: the version number of the application (and datamodel) are stored as a module named ROOT_MODULE
-
- $sModuleLabel = $aModule['label'];
- $sModuleHelp = $aModule['doc.more_information'];
- $sMoreInfo = (!empty($aModule['doc.more_information'])) ? "more info": '';
- if (($aModule['category'] != 'authentication') && ($aModule['visible'] && !isset($aModule['auto_select'])))
+
+ foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
{
- if (($bAddExtensionsOnly) && (!$this->IsExtension($aModule))) continue;
-
- if ($this->IsExtension($aModule))
+ if ($oExtension->sSource !== iTopExtension::SOURCE_WIZARD)
{
- $iStepIndex = count($aSteps) - 1;
+ $aStepDefinition['options'][] = array(
+ 'extension_code' => $oExtension->sCode,
+ 'title' => $oExtension->sLabel,
+ 'description' => $oExtension->sDescription,
+ 'more_info' => $oExtension->sMoreInfoUrl,
+ 'default' => true, // by default offer to install all modules
+ 'modules' => $oExtension->aModules,
+ 'mandatory' => $oExtension->bMandatory || ($oExtension->sSource === iTopExtension::SOURCE_REMOTE),
+ 'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
+ );
}
- else
- {
- $iStepIndex = 0;
- }
- $aSteps[$iStepIndex]['options'][] = array(
- 'title' => $sModuleLabel,
- 'description' => '',
- 'more_info' => $sMoreInfo,
- 'default' => true, // by default offer to install all modules
- 'modules' => array($sModuleId),
- 'mandatory' => ($aModule['install']['flag'] & MODULE_ACTION_MANDATORY) ? true : false,
- );
}
- }
-
- if (count($aSteps[count($aSteps) - 1]['options']) == 0)
- {
- // No extensions at all, remove the last step
- $this->oWizard->SetParameter('additional_extensions_modules', '[]');
- array_pop($aSteps);
+ // Display this step of the wizard only if there is something to display
+ if (count($aStepDefinition['options']) !== 0)
+ {
+ $aSteps[] = $aStepDefinition;
+ $this->oWizard->SetParameter('additional_extensions_modules', json_encode($aStepDefinition['options']));
+ }
+
}
else
{
- $this->oWizard->SetParameter('additional_extensions_modules', json_encode($aSteps[count($aSteps) - 1]['options']));
+ // No wizard configuration provided, build a standard one with just one big list
+ $aStepDefinition = array(
+ 'title' => 'Modules Selection',
+ 'description' => 'Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.
',
+ 'banner' => '/images/modules.png',
+ 'options' => array()
+ );
+ foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
+ {
+ if ($oExtension->sSource)
+ {
+ $aStepDefinition['options'][] = array(
+ 'extension_code' => $oExtension->sCode,
+ 'title' => $oExtension->sLabel,
+ 'description' => $oExtension->sDescription,
+ 'more_info' => $oExtension->sMoreInfoUrl,
+ 'default' => true, // by default offer to install all modules
+ 'modules' => $oExtension->aModules,
+ 'mandatory' => $oExtension->bMandatory || ($oExtension->sSource !== iTopExtension::SOURCE_REMOTE),
+ 'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
+ );
+ }
+ }
+ $aSteps[] = $aStepDefinition;
}
if (array_key_exists($index, $aSteps))
@@ -1702,52 +1817,70 @@ EOF
return $aStepInfo;
}
- protected function GetExtensionsStepInfo()
+ protected function GetExtensionSourceLabel($sSource)
{
- // let the user select from the list of modules located in the "extensions" folder
+ switch($sSource)
+ {
+ case iTopExtension::SOURCE_MANUAL:
+ $sResult = 'Extension';
+ break;
+
+ case iTopExtension::SOURCE_REMOTE:
+ $sResult = (ITOP_APPLICATION == 'iTop') ? 'iTop-Hub' : 'ITSM-Designer';
+ break;
+
+ default:
+ $sResult = '';
+ }
+ if ($sResult == '')
+ {
+ return '';
+ }
+ return ''.$sResult.'';
}
- protected function IsExtension($aModule)
- {
- // root_dir is the directory containing the module, check if its parent is "extensions"
- if (basename(dirname($aModule['root_dir'])) == 'extensions')
- {
- return true;
- }
- return false;
- }
-
- protected function DisplayOptions($oPage, $aStepInfo, $aSelectedComponents, $aDefaults, $sParentId = '')
+ protected function DisplayOptions($oPage, $aStepInfo, $aSelectedComponents, $aDefaults, $sParentId = '', $bAllDisabled = false)
{
$aOptions = isset($aStepInfo['options']) ? $aStepInfo['options'] : array();
$aAlternatives = isset($aStepInfo['alternatives']) ? $aStepInfo['alternatives'] : array();
$index = 0;
+
+ $sAllDisabled = '';
+ if ($bAllDisabled)
+ {
+ $sAllDisabled = 'disabled data-disabled="disabled" ';
+ }
foreach($aOptions as $index => $aChoice)
{
$sAttributes = '';
$sChoiceId = $sParentId.self::$SEP.$index;
+ $sDataId = 'data-id="'.htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8').'"';
+ $sId = htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8');
$bIsDefault = array_key_exists($sChoiceId, $aDefaults);
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault);
+ $bDisabled = false;
if ($bMandatory)
{
- $oPage->add(' ');
+ $oPage->add('
');
+ $bDisabled = true;
}
else if ($bSelected)
{
- $oPage->add('
');
+ $oPage->add('
');
}
else
{
- $oPage->add('
');
+ $oPage->add('
');
}
- $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId);
+ $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled);
$oPage->add('
');
$index++;
}
$sChoiceName = null;
$sDisabled = '';
+ $bDisabled = false;
$sChoiceIdNone = null;
foreach($aAlternatives as $index => $aChoice)
{
@@ -1758,21 +1891,32 @@ EOF
}
$bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId);
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault);
- if ($bMandatory)
+ if ($bMandatory || $bAllDisabled)
{
// One choice is mandatory, all alternatives are disabled
$sDisabled = ' disabled data-disabled="disabled"';
+ $bDisabled = true;
}
if ( (!isset($aChoice['sub_options']) || (count($aChoice['sub_options']) == 0)) && (!isset($aChoice['modules']) || (count($aChoice['modules']) == 0)) )
{
$sChoiceIdNone = $sChoiceId; // the "None" / empty choice
}
}
-
+
+ if (!array_key_exists($sChoiceName, $aDefaults) || ($aDefaults[$sChoiceName] == $sChoiceIdNone))
+ {
+ // The "none" choice does not disable the selection !!
+ $sDisabled = '';
+ $bDisabled = false;
+ }
+
foreach($aAlternatives as $index => $aChoice)
{
$sAttributes = '';
$sChoiceId = $sParentId.self::$SEP.$index;
+ $sDataId = 'data-id="'.htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8').'"';
+ $sId = htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8');
+
if ($sChoiceName == null)
{
$sChoiceName = $sChoiceId; // All radios share the same name
@@ -1796,22 +1940,24 @@ EOF
$sAttributes = ' checked ';
$sHidden = '
';
}
- $oPage->add('
'.$sHidden.' ');
- $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId);
+ $oPage->add('
'.$sHidden.' ');
+ $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled);
$oPage->add('
');
$index++;
}
}
- protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId)
+ protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false)
{
- $sMoreInfo = isset($aChoice['more_info']) ? $aChoice['more_info'] : '';
- $oPage->add('
'.$sMoreInfo);
+ $sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '
More information' : '';
+ $sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : '';
+ $sId = htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8');
+ $oPage->add('
'.$sMoreInfo);
$sDescription = isset($aChoice['description']) ? htmlentities($aChoice['description'], ENT_QUOTES, 'UTF-8') : '';
$oPage->add('
'.$sDescription.'');
if (isset($aChoice['sub_options']))
{
- $this->DisplayOptions($oPage, $aChoice['sub_options'], $aSelectedComponents, $aDefaults, $sChoiceId);
+ $this->DisplayOptions($oPage, $aChoice['sub_options'], $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled);
}
$oPage->add('
');
}
@@ -1946,8 +2092,6 @@ EOF
$aInstallParams = $this->BuildConfig();
$sMode = $aInstallParams['mode'];
-
- $sPreinstallationPhase = '';
$sDestination = ITOP_APPLICATION.(($sMode == 'install') ? ' version '.ITOP_VERSION.' is about to be installed ' : ' is about to be upgraded ');
$sDBDescription = '
existing database
'.$aInstallParams['database']['name'].'';
@@ -2081,6 +2225,7 @@ EOF
{
$sMode = $this->oWizard->GetParameter('install_mode', 'install');
$aSelectedModules = json_decode($this->oWizard->GetParameter('selected_modules'), true);
+ $aSelectedExtensions = json_decode($this->oWizard->GetParameter('selected_extensions'), true);
$sBackupDestination = '';
$sPreviousConfigurationFile = '';
$sDBName = $this->oWizard->GetParameter('db_name');
@@ -2165,6 +2310,7 @@ EOF
),
'language' => $this->oWizard->GetParameter('default_language'),
'selected_modules' => $aSelectedModules,
+ 'selected_extensions' => $aSelectedExtensions,
'sample_data' => ($this->oWizard->GetParameter('sample_data', '') == 'yes') ? true : false ,
'old_addon' => $this->oWizard->GetParameter('old_addon', false), // whether or not to use the "old" userrights profile addon
'options' => json_decode($this->oWizard->GetParameter('misc_options', '[]'), true),
@@ -2203,7 +2349,7 @@ EOF
//$("#percentage").html('{$aRes['percentage-completed']} % completed
{$aRes['next-step-label']}');
ExecuteStep('{$aRes['next-step']}');
EOF
- );
+ );
}
else if ($aRes['status'] != ApplicationInstaller::ERROR)
{
@@ -2318,11 +2464,31 @@ class WizStepDone extends WizardStep
}
// Form goes here.. No back button since the job is done !
- $oPage->add('
');
+ $oPage->add('');
$oPage->add(" | ");
$oPage->add(" | ");
$oPage->add(" | ");
$oPage->add('
');
+
+ $oConfig = new Config(utils::GetConfigFilePath());
+ $sIframeUrl = $oConfig->GetModuleSetting('itop-hub-connector', 'setup_url', '');
+
+ if ($sIframeUrl != '')
+ {
+ $sIframeUrl .= '?';
+ $oPage->add('');
+
+ $oPage->add_script("window.addEventListener('message', function(event) {
+ if (event.data === 'itophub_load_completed')
+ {
+ $('#fresh_content').height($('#placeholder').height());
+ $('#placeholder').hide();
+ $('#fresh_content').show();
+ }
+ }, false);
+ ");
+ }
+
$sForm = '