Improved way to track the choices made during the installation in order to:

1) Be able to proerly report this information
2) Make sure that the same (proper) choices are proposed upon update

SVN:trunk[4815]
This commit is contained in:
Denis Flaven
2017-07-07 16:00:30 +00:00
parent 307145502c
commit d0d9b1ce50
12 changed files with 938 additions and 143 deletions

View File

@@ -1962,3 +1962,11 @@ span.refresh-button {
.object-ref-link {
background: none;
}
.extension-source {
display: inline-block;
background-color: #555;
padding: 3px;
font-size: 10px;
color: #fff;
border-radius: 4px;
}

View File

@@ -2163,3 +2163,11 @@ span.refresh-button {
.object-ref-link {
background: none;
}
.extension-source {
display:inline-block;
background-color: $grey-color;
padding:3px;
font-size:10px;
color:#fff;
border-radius: 4px;
}

View File

@@ -1305,7 +1305,10 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:About:DataModel' => 'Data model',
'UI:About:Support' => 'Support information',
'UI:About:Licenses' => 'Licenses',
'UI:About:Modules' => 'Installed modules',
'UI:About:InstallationOptions' => 'Installation options',
'UI:About:ManualExtensionSource' => 'Extension',
'UI:About:Extension_Version' => 'Version: %1$s',
'UI:About:RemoteExtensionSource' => 'Data',
'UI:DisconnectedDlgMessage' => 'You are disconnected. You must identify yourself to continue using the application.',
'UI:DisconnectedDlgTitle' => 'Warning!',

View File

@@ -1148,7 +1148,10 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:About:DataModel' => 'Modèle de données',
'UI:About:Support' => 'Informations pour le support',
'UI:About:Licenses' => 'Licences',
'UI:About:Modules' => 'Modules installés',
'UI:About:InstallationOptions' => 'Options d\'installation',
'UI:About:Extension_Version' => 'Version: %1$s',
'UI:About:ManualExtensionSource' => 'Extension',
'UI:DisconnectedDlgMessage' => 'Vous êtes déconnecté(e). Vous devez vous identifier pour pouvoir continuer à utiliser l\'application.',
'UI:DisconnectedDlgTitle' => 'Attention !',

View File

@@ -1231,16 +1231,30 @@ EOF
$oPage->add("</div>");
$oPage->add('<fieldset>');
$oPage->add('<legend>'.Dict::S('UI:About:Modules').'</legend>');
//$oPage->add(print_r($aAvailableModules, true));
$oPage->add("<div style=\"height: 150px; overflow: auto; font-size: smaller;\">");
$oPage->add('<legend>'.Dict::S('UI:About:InstallationOptions').'</legend>');
$oPage->add("<div style=\"max-height: 150px; overflow: auto; font-size: smaller;\">");
$oPage->add('<ul style="margin: 0;">');
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('<li>'.$aModuleData['label'].' ('.$aModuleData['version_db'].')</li>');
switch($oExtension->sSource)
{
case iTopExtension::SOURCE_REMOTE:
$sSource = ' <span class="extension-source">'.Dict::S('UI:About:RemoteExtensionSource').'</span>';
break;
case iTopExtension::SOURCE_MANUAL:
$sSource = ' <span class="extension-source">'.Dict::S('UI:About:ManualExtensionSource').'</span>';
break;
default:
$sSource = '';
}
$oPage->add('<li title="'.Dict::Format('UI:About:Extension_Version', $oExtension->sInstalledVersion).'">'.$oExtension->sLabel.$sSource.'</li>');
}
$oPage->add('</ul>');
$oPage->add("</div>");
@@ -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;

View File

@@ -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");
}

View File

@@ -0,0 +1,458 @@
<?php
require_once(APPROOT.'/setup/parameters.class.inc.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
/**
* Basic helper class to describe an extension, with some characteristics and a list of modules
*/
class iTopExtension
{
const SOURCE_WIZARD = 'datamodels';
const SOURCE_MANUAL = 'extensions';
const SOURCE_REMOTE = 'data';
/**
* @var string
*/
public $sCode;
/**
* @var string
*/
public $sVersion;
/**
* @var string
*/
public $sInstalledVersion;
/**
* @var string
*/
public $sLabel;
/**
* @var string
*/
public $sDescription;
/**
* @var string
*/
public $sSource;
/**
* @var bool
*/
public $bMandatory;
/**
* @var string
*/
public $sMoreInfoUrl;
/**
* @var bool
*/
public $bMarkedAsChosen;
/**
* @var string[]
*/
public $aModules;
public function __construct()
{
$this->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('<?php', '?>'), '', $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;
}
}

View File

@@ -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)

View File

@@ -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 <name>/<optional_operator><version>
// 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 {
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,10 +18,10 @@
/**
* Persistent class Event and derived
* Persistent class ModuleInstallation to record the installed modules
* Log of module installations
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2017 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -59,4 +59,44 @@ class ModuleInstallation extends cmdbAbstractObject
}
}
?>
/**
* 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
}
}

View File

@@ -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,12 +346,27 @@ 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/<target-env>-modules)
// The actual choices will be recorded by RecordInstallation below
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
{
if($oExtension->sSource == iTopExtension::SOURCE_REMOTE)
{
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
}
}
// Do load the required modules
//
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
@@ -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;
@@ -703,6 +738,30 @@ class RunTimeEnvironment
$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);

View File

@@ -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 '<div style="display:block;position:fixed;width:100px;height:20px;top:0;left:0;font-size:10pt;">Default: '.($this->bChoicesFromDatabase ? 'DB' : 'Guess').'</div>';
}
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 = '<ul>';
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 .= '</ul>';
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 "<pre>aStepInfo:\n ".print_r($aStepInfo, true)."</pre>";
//echo "<pre>aDefaults:\n ".print_r($aDefaults, true)."</pre>";
@@ -1352,7 +1391,81 @@ EOF
);
}
protected function GetDefaults($aInfo, &$aDefaults, $aModules, $sParentId = '')
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);
}
}
$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 .= '<ul>';
$sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices);
$sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices, $aSelectedExtensions);
$sDisplayChoices .= '</ul>';
}
$sDisplayChoices .= '</li>';
@@ -1533,6 +1655,10 @@ EOF
(isset($aSelectedChoices[$sChoiceName]) && ($aSelectedChoices[$sChoiceName] == $sChoiceId)) )
{
$sDisplayChoices .= '<li>'.$aChoice['title'].'</li>';
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 .= '<ul>';
$sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices);
$sDisplayChoices = $this->GetSelectedModules($aChoice['sub_options'], $aSelectedChoices, $aModules, $sChoiceId, $sDisplayChoices, $aSelectedExtensions);
$sDisplayChoices .= '</ul>';
}
$sDisplayChoices .= '</li>';
@@ -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' => '<h2>Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.</h2>',
'banner' => '/images/modules.png',
'options' => array()
);
}
// Additional step for the extensions
$aSteps[] = array(
// Additional step for the "extensions"
$aStepDefinition = array(
'title' => 'Extensions',
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.</h2>',
'banner' => '/images/extension.png',
'options' => array()
);
try
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
{
$sDefaultAppPath = utils::GetDefaultUrlAppRoot();
}
catch(Exception $e)
if ($oExtension->sSource !== iTopExtension::SOURCE_WIZARD)
{
$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'])) ? "<a href=\"$sDefaultAppPath{$aModule['doc.more_information']}\" target=\"_blank\">more info</a>": '';
if (($aModule['category'] != 'authentication') && ($aModule['visible'] && !isset($aModule['auto_select'])))
{
if (($bAddExtensionsOnly) && (!$this->IsExtension($aModule))) continue;
if ($this->IsExtension($aModule))
{
$iStepIndex = count($aSteps) - 1;
}
else
{
$iStepIndex = 0;
}
$aSteps[$iStepIndex]['options'][] = array(
'title' => $sModuleLabel,
'description' => '',
'more_info' => $sMoreInfo,
$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' => array($sModuleId),
'mandatory' => ($aModule['install']['flag'] & MODULE_ACTION_MANDATORY) ? true : false,
'modules' => $oExtension->aModules,
'mandatory' => $oExtension->bMandatory || ($oExtension->sSource === iTopExtension::SOURCE_REMOTE),
'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
);
}
}
if (count($aSteps[count($aSteps) - 1]['options']) == 0)
// Display this step of the wizard only if there is something to display
if (count($aStepDefinition['options']) !== 0)
{
// No extensions at all, remove the last step
$this->oWizard->SetParameter('additional_extensions_modules', '[]');
array_pop($aSteps);
$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' => '<h2>Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.</h2>',
'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 '<span style="display:inline-block;font-size:8pt;padding:3px;border-radius:4px;color:#fff;background-color:#1c94c4;margin-left:0.5em;margin-right:0.5em">'.$sResult.'</span>';
}
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('<div class="choice"><input id="choice'.$sChoiceId.'" checked disabled data-disabled="disabled" type="checkbox"'.$sAttributes.'/><input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'">&nbsp;');
$oPage->add('<div class="choice" '.$sDataId.'><input id="'.$sId.'" checked disabled data-disabled="disabled" type="checkbox"'.$sAttributes.'/><input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'">&nbsp;');
$bDisabled = true;
}
else if ($bSelected)
{
$oPage->add('<div class="choice"><input class="wiz-choice" id="choice'.$sChoiceId.'" name="choice['.$sChoiceId.']" type="checkbox" checked value="'.$sChoiceId.'"/>&nbsp;');
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" '.$sAllDisabled.'id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" checked value="'.$sChoiceId.'"/>&nbsp;');
}
else
{
$oPage->add('<div class="choice"><input class="wiz-choice" id="choice'.$sChoiceId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'"/>&nbsp;');
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" '.$sAllDisabled.'id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'"/>&nbsp;');
}
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId);
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled);
$oPage->add('</div>');
$index++;
}
$sChoiceName = null;
$sDisabled = '';
$bDisabled = false;
$sChoiceIdNone = null;
foreach($aAlternatives as $index => $aChoice)
{
@@ -1758,10 +1891,11 @@ 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)) )
{
@@ -1769,10 +1903,20 @@ EOF
}
}
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 = '<input type="hidden" name="choice['.$sChoiceName.']" value="'.$sChoiceId.'"/>';
}
$oPage->add('<div class="choice"><input class="wiz-choice" id="choice'.$sChoiceId.'" name="choice['.$sChoiceName.']" type="radio"'.$sAttributes.' value="'.$sChoiceId.'"'.$sDisabled.'/>'.$sHidden.'&nbsp;');
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId);
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" id="'.$sId.'" name="choice['.$sChoiceName.']" type="radio"'.$sAttributes.' value="'.$sChoiceId.'"'.$sDisabled.'/>'.$sHidden.'&nbsp;');
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled);
$oPage->add('</div>');
$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('<label for="choice'.$sChoiceId.'"><b>'.htmlentities($aChoice['title'], ENT_QUOTES, 'UTF-8').'</b></label> '.$sMoreInfo);
$sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '<a target="_blank" href="'.$aChoice['more_info'].'">More information</a>' : '';
$sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : '';
$sId = htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8');
$oPage->add('<label for="'.$sId.'"><b>'.htmlentities($aChoice['title'], ENT_QUOTES, 'UTF-8').'</b>'.$sSourceLabel.'</label> '.$sMoreInfo);
$sDescription = isset($aChoice['description']) ? htmlentities($aChoice['description'], ENT_QUOTES, 'UTF-8') : '';
$oPage->add('<div class="description">'.$sDescription.'<span id="sub_choices'.$sChoiceId.'">');
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('</span></div>');
}
@@ -1947,8 +2093,6 @@ EOF
$sMode = $aInstallParams['mode'];
$sPreinstallationPhase = '';
$sDestination = ITOP_APPLICATION.(($sMode == 'install') ? ' version '.ITOP_VERSION.' is about to be installed ' : ' is about to be upgraded ');
$sDBDescription = ' <b>existing</b> database <b>'.$aInstallParams['database']['name'].'</b>';
if (($sMode == 'install') && ($this->oWizard->GetParameter('create_db') == 'yes'))
@@ -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),
@@ -2318,11 +2464,31 @@ class WizStepDone extends WizardStep
}
// Form goes here.. No back button since the job is done !
$oPage->add('<table style="width:600px;border:0;padding:0;"><tr>');
$oPage->add('<table id="placeholder" style="width:600px;border:0;padding:0;"><tr>');
$oPage->add("<td><a style=\"background:transparent;padding:0;\" title=\"Free: Register your iTop version.\" href=\"http://www.combodo.com/register?product=iTop&version=".urlencode(ITOP_VERSION." revision ".ITOP_REVISION)."\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-register.gif\"/></td></a>");
$oPage->add("<td><a style=\"background:transparent;padding:0;\" title=\"Get Professional Support from Combodo\" href=\"http://www.combodo.com/itopsupport\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-support.gif\"/></td></a>");
$oPage->add("<td><a style=\"background:transparent;padding:0;\" title=\"Get Professional Training from Combodo\" href=\"http://www.combodo.com/itoptraining\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-training.gif\"/></td></a>");
$oPage->add('</tr></table>');
$oConfig = new Config(utils::GetConfigFilePath());
$sIframeUrl = $oConfig->GetModuleSetting('itop-hub-connector', 'setup_url', '');
if ($sIframeUrl != '')
{
$sIframeUrl .= '?';
$oPage->add('<iframe id="fresh_content" style="border:0; width:100%; display:none;" src="'.$sIframeUrl.'"></iframe>');
$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 = '<form method="post" action="'.$this->oWizard->GetParameter('application_url').'pages/UI.php">';
$sForm .= '<input type="hidden" name="auth_user" value="'.htmlentities($this->oWizard->GetParameter('admin_user'), ENT_QUOTES, 'UTF-8').'">';
$sForm .= '<input type="hidden" name="auth_pwd" value="'.htmlentities($this->oWizard->GetParameter('admin_pwd'), ENT_QUOTES, 'UTF-8').'">';
@@ -2380,7 +2546,11 @@ class WizStepDone extends WizardStep
{
if (in_array('_'.$idx, $aParameters[count($aParameters)-1]))
{
$aAdditionalModules[] = $aModuleInfo['modules'][0]; // Extensions "choices" are always made of one module
// Extensions "choices" can now have more than one module
foreach($aModuleInfo['modules'] as $sModuleName)
{
$aAdditionalModules[] = $sModuleName;
}
}
}
$idx = 0;
@@ -2417,7 +2587,7 @@ class WizStepDone extends WizardStep
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
{
// For security reasons: add the extension now so that this action can be used to read *only* .zip files from the disk...
// For security reasons: add the extension now so that this action can be used to read *only* .tar.gz files from the disk...
$sBackupFile = $aParameters['backup'].'.tar.gz';
if (file_exists($sBackupFile))
{