mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 15:34:12 +01:00
Compare commits
27 Commits
feature/89
...
feature/87
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3e4010cd0 | ||
|
|
f8072f6422 | ||
|
|
67013df7e7 | ||
|
|
0234f4d12b | ||
|
|
e35e7859ba | ||
|
|
d6f37b5197 | ||
|
|
65065026b1 | ||
|
|
54c7af1140 | ||
|
|
02ea17d897 | ||
|
|
6ab5722286 | ||
|
|
9579c090a2 | ||
|
|
64274641a5 | ||
|
|
bb6248a6e7 | ||
|
|
130d98aa3f | ||
|
|
00c590232a | ||
|
|
97828225db | ||
|
|
03e59c9749 | ||
|
|
985a49dc9f | ||
|
|
adae35ccc4 | ||
|
|
f0c9629f5f | ||
|
|
4e96b297c2 | ||
|
|
ae620c6663 | ||
|
|
b5c51a2983 | ||
|
|
3b2d845c00 | ||
|
|
36c545a6c4 | ||
|
|
5ecb4936f0 | ||
|
|
cfc933b92b |
@@ -16,5 +16,5 @@ require_once(APPROOT.'/application/audit.category.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.domain.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.rule.class.inc.php');
|
||||
require_once(APPROOT.'/application/query.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
|
||||
@@ -24,7 +24,7 @@ MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.domain.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
|
||||
@@ -2766,7 +2766,7 @@ class Config
|
||||
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
|
||||
$aAddOns = $oEmptyConfig->GetAddOns();
|
||||
|
||||
$aModules = ModuleDiscovery::GetAvailableModules([APPROOT.$sModulesDir]);
|
||||
$aModules = ModuleDiscovery::GetModulesOrderedByDependencies([APPROOT.$sModulesDir]);
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules)) {
|
||||
|
||||
@@ -22,6 +22,8 @@ use Combodo\iTop\Application\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
|
||||
require_once APPROOT.'core/modulehandler.class.inc.php';
|
||||
require_once APPROOT.'core/querymodifier.class.inc.php';
|
||||
@@ -468,11 +470,35 @@ abstract class MetaModel
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetCreatedIn($sClass)
|
||||
final public static function GetModuleName($sClass)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
try {
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$sDir = realpath(dirname($oReflectionClass->getFileName()));
|
||||
$sApproot = realpath(APPROOT);
|
||||
while (($sDir !== $sApproot) && (str_contains($sDir, $sApproot))) {
|
||||
$aFiles = glob("$sDir/module.*.php");
|
||||
if (count($aFiles) > 1) {
|
||||
return 'core';
|
||||
}
|
||||
|
||||
return self::$m_aClassParams[$sClass]["created_in"] ?? "";
|
||||
if (count($aFiles) == 0) {
|
||||
$sDir = realpath(dirname($sDir));
|
||||
continue;
|
||||
}
|
||||
|
||||
$sModuleFilePath = $aFiles[0];
|
||||
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
|
||||
list($sModuleName, ) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
return $sModuleName;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new CoreException("Cannot find class module", ['class' => $sClass], '', $e);
|
||||
}
|
||||
|
||||
return 'core';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3158,7 +3184,6 @@ abstract class MetaModel
|
||||
$aMandatParams = [
|
||||
"category" => "group classes by modules defining their visibility in the UI",
|
||||
"key_type" => "autoincrement | string",
|
||||
//"created_in" => "module_name where class is defined",
|
||||
"name_attcode" => "define which attribute is the class name, may be an array of attributes (format specified in the dictionary as 'Class:myclass/Name' => '%1\$s %2\$s...'",
|
||||
"state_attcode" => "define which attribute is representing the state (object lifecycle)",
|
||||
"reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -605,6 +605,7 @@ body {
|
||||
color:#a00000;
|
||||
}
|
||||
.setup-extension-tag {
|
||||
display: inline-flex;
|
||||
background-color: grey;
|
||||
border-radius: 8px;
|
||||
padding-left: 3px;
|
||||
|
||||
@@ -517,7 +517,7 @@ class ApplicationInstaller
|
||||
*
|
||||
* @since 3.1.0 N°2013 added the aParamValues param
|
||||
*/
|
||||
protected function DoCompile($aRemovedExtensionCodes, $aSelectedModules, $sSourceDir, $sExtensionDir, $bIsSetupDataAuditEnabled, $bUseSymbolicLinks = null)
|
||||
protected function DoCompile($aRemovedExtensionCodes, $aSelectedModules, $sSourceDir, $sExtensionDir, bool &$bIsSetupDataAuditEnabled, $bUseSymbolicLinks = null)
|
||||
{
|
||||
/**
|
||||
* @since 3.2.0 move the ContextTag init at the very beginning of the method
|
||||
@@ -565,7 +565,7 @@ class ApplicationInstaller
|
||||
$bIsAlreadyInMaintenanceMode = false;
|
||||
}
|
||||
|
||||
$this->SaveModelInfo($sEnvironment);
|
||||
$bIsSetupDataAuditEnabled = $this->SaveModelInfo($sEnvironment);
|
||||
}
|
||||
|
||||
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
|
||||
@@ -664,11 +664,17 @@ class ApplicationInstaller
|
||||
return APPROOT."data/beforecompilation_".$sEnv."_modelinfo.json";
|
||||
}
|
||||
|
||||
private function SaveModelInfo(string $sEnvironment): void
|
||||
private function SaveModelInfo(string $sEnvironment): bool
|
||||
{
|
||||
$aModelInfo = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($sEnvironment);
|
||||
$sModelInfoPath = $this->GetModelInfoPath($sEnvironment);
|
||||
file_put_contents($sModelInfoPath, json_encode($aModelInfo));
|
||||
try {
|
||||
$aModelInfo = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($sEnvironment);
|
||||
} catch (Exception $e) {
|
||||
//logged already
|
||||
return is_file($sModelInfoPath);
|
||||
}
|
||||
|
||||
return (bool) file_put_contents($sModelInfoPath, json_encode($aModelInfo));
|
||||
}
|
||||
|
||||
private function GetPreviousModelInfo(string $sEnvironment): array
|
||||
|
||||
@@ -477,7 +477,7 @@ class MFCompiler
|
||||
$sClass = $oClass->getAttribute("id");
|
||||
$aAllClasses[] = $sClass;
|
||||
try {
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
} catch (DOMFormatException $e) {
|
||||
$sMessage = "Failed to process class '$sClass', ";
|
||||
if (!empty($sModuleRootDir)) {
|
||||
@@ -1189,7 +1189,6 @@ EOF
|
||||
|
||||
/**
|
||||
* @param \MFElement $oClass
|
||||
* @param string $sModuleName
|
||||
* @param string $sTempTargetDir
|
||||
* @param string $sFinalTargetDir
|
||||
* @param string $sModuleRelativeDir
|
||||
@@ -1197,7 +1196,7 @@ EOF
|
||||
* @return string
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
protected function CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
protected function CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
{
|
||||
$sClass = $oClass->getAttribute('id');
|
||||
$oProperties = $oClass->GetUniqueElement('properties');
|
||||
@@ -1210,7 +1209,6 @@ EOF
|
||||
$aClassParams = [];
|
||||
$aClassParams['category'] = $this->GetPropString($oProperties, 'category', '');
|
||||
$aClassParams['key_type'] = "'autoincrement'";
|
||||
$aClassParams['created_in'] = "'$sModuleName'";
|
||||
if ((bool)$this->GetPropNumber($oProperties, 'is_link', 0)) {
|
||||
$aClassParams['is_link'] = 'true';
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ class iTopExtensionsMap
|
||||
}
|
||||
}
|
||||
|
||||
\ModuleDiscovery::DeclareRemovedExtensions($aRemovedExtension);
|
||||
ModuleDiscovery::DeclareRemovedExtensions($aRemovedExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,7 +329,7 @@ class iTopExtensionsMap
|
||||
$aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs);
|
||||
|
||||
try {
|
||||
ModuleDiscovery::GetAvailableModules($aSearchDirs, true);
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true);
|
||||
} catch (MissingDependencyException $e) {
|
||||
// Some modules have missing dependencies
|
||||
// Let's check what is the impact at the "extensions" level
|
||||
@@ -369,6 +369,75 @@ class iTopExtensionsMap
|
||||
return array_merge($this->aInstalledExtensions ?? [], $this->aExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bKeepMissingDependencyExtensions
|
||||
*
|
||||
* @return array<\iTopExtension>>
|
||||
*/
|
||||
|
||||
public function GetAllExtensionsToDisplayInSetup(bool $bKeepMissingDependencyExtensions = false): array
|
||||
{
|
||||
$aRes = [];
|
||||
foreach ($this->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
if (($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) && ($oExtension->bVisible)) {
|
||||
if ($bKeepMissingDependencyExtensions || (count($oExtension->aMissingDependencies) == 0)) {
|
||||
if (!$oExtension->bMandatory) {
|
||||
$oExtension->bMandatory = ($oExtension->sSource === iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
$aRes[$oExtension->sCode] = $oExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
public function GetAllExtensionsOptionInfo(): array
|
||||
{
|
||||
$aRes = [];
|
||||
foreach ($this->GetAllExtensionsToDisplayInSetup() as $sCode => $oExtension) {
|
||||
$aRes[] = [
|
||||
'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,
|
||||
'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
|
||||
'uninstallable' => $oExtension->CanBeUninstalled(),
|
||||
'missing' => $oExtension->bRemovedFromDisk,
|
||||
];
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
protected function GetExtensionSourceLabel($sSource)
|
||||
{
|
||||
$sDecorationClass = '';
|
||||
switch ($sSource) {
|
||||
case iTopExtension::SOURCE_MANUAL:
|
||||
$sResult = 'Local extensions folder';
|
||||
$sDecorationClass = 'fas fa-folder';
|
||||
break;
|
||||
|
||||
case iTopExtension::SOURCE_REMOTE:
|
||||
$sResult = (ITOP_APPLICATION == 'iTop') ? 'iTop Hub' : 'ITSM Designer';
|
||||
$sDecorationClass = (ITOP_APPLICATION == 'iTop') ? 'fc fc-chameleon-icon' : 'fa pencil-ruler';
|
||||
break;
|
||||
|
||||
default:
|
||||
$sResult = '';
|
||||
}
|
||||
if ($sResult == '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<i class="setup-extension--icon '.$sDecorationClass.'" data-tooltip-content="'.$sResult.'"></i>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given extension as chosen
|
||||
* @param string $sExtensionCode The code of the extension (code without version number)
|
||||
@@ -454,7 +523,7 @@ class iTopExtensionsMap
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function LoadInstalledExtensionsFromDatabase(Config $oConfig): array|false
|
||||
public function LoadInstalledExtensionsFromDatabase(Config $oConfig): array|false
|
||||
{
|
||||
try {
|
||||
if (CMDBSource::DBName() === null) {
|
||||
@@ -497,6 +566,27 @@ class iTopExtensionsMap
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetChoicesFromDatabase(Config $oConfig): array|false
|
||||
{
|
||||
try {
|
||||
if (CMDBSource::DBName() === null) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
}
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_extension_install");
|
||||
$aDBInfo = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
|
||||
|
||||
$aChoices = [];
|
||||
foreach ($aDBInfo as $aExtensionInfo) {
|
||||
$aChoices[] = $aExtensionInfo['label'];
|
||||
}
|
||||
|
||||
return $aChoices;
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given module name is "chosen" since it is part of a "chosen" extension (in the specified source dir)
|
||||
* @param string $sModuleNameToFind
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Config;
|
||||
use InstallationChoicesToModuleConverter;
|
||||
use iTopExtensionsMap;
|
||||
use MetaModel;
|
||||
use ModuleDiscovery;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
@@ -37,14 +42,22 @@ class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
|
||||
$sEnv = $this->sFinalEnv;
|
||||
$this->aExtensionsByCode = $aExtensionCodesToRemove;
|
||||
//SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
|
||||
|
||||
$this->Cleanup();
|
||||
SetupUtils::copydir(APPROOT."/data/$sSourceEnv-modules", APPROOT."/data/$sEnv-modules");
|
||||
|
||||
$this->DeclareExtensionAsRemoved($aExtensionCodesToRemove);
|
||||
|
||||
$oDryRemovalConfig = clone(MetaModel::GetConfig());
|
||||
$oDryRemovalConfig->ChangeModulesPath($sSourceEnv, $this->sFinalEnv);
|
||||
$this->WriteConfigFileSafe($oDryRemovalConfig);
|
||||
|
||||
$sSourceDir = $oDryRemovalConfig->Get('source_dir');
|
||||
$aSearchDirs = $this->GetExtraDirsToCompile($sSourceDir);
|
||||
|
||||
$aModulesToLoad = $this->GetModulesToLoad($sSourceEnv, $aSearchDirs);
|
||||
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true, $aModulesToLoad);
|
||||
}
|
||||
|
||||
private function DeclareExtensionAsRemoved(array $aExtensionCodes): void
|
||||
@@ -53,6 +66,27 @@ class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aExtensionCodes);
|
||||
}
|
||||
|
||||
private function GetModulesToLoad(string $sSourceEnv, $aSearchDirs): array
|
||||
{
|
||||
$oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv));
|
||||
$aChoices = iTopExtensionsMap::GetChoicesFromDatabase($oSourceConfig);
|
||||
$sSourceDir = $oSourceConfig->Get('source_dir');
|
||||
|
||||
$sInstallFilePath = APPROOT.$sSourceDir.'/installation.xml';
|
||||
if (! is_file($sInstallFilePath)) {
|
||||
$sInstallFilePath = null;
|
||||
}
|
||||
|
||||
$aModuleIdsToLoad = InstallationChoicesToModuleConverter::GetInstance()->GetModules($aChoices, $aSearchDirs, $sInstallFilePath);
|
||||
$aModulesToLoad = [];
|
||||
foreach ($aModuleIdsToLoad as $sModuleId) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
$aModulesToLoad[] = $sModuleName;
|
||||
}
|
||||
return $aModulesToLoad;
|
||||
}
|
||||
|
||||
public function Cleanup()
|
||||
{
|
||||
$sEnv = $this->sFinalEnv;
|
||||
|
||||
@@ -135,8 +135,9 @@ class iTopExtension
|
||||
return $this->bCanBeUninstalled;
|
||||
}
|
||||
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
|
||||
$this->bCanBeUninstalled = $aModuleInfo['uninstallable'] === 'yes';
|
||||
return $this->bCanBeUninstalled;
|
||||
if ($aModuleInfo['uninstallable'] !== 'yes') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1801,7 +1801,7 @@ EOF
|
||||
*/
|
||||
public function FindModules()
|
||||
{
|
||||
$aAvailableModules = ModuleDiscovery::GetAvailableModules($this->aRootDirs);
|
||||
$aAvailableModules = ModuleDiscovery::GetModulesOrderedByDependencies($this->aRootDirs);
|
||||
$aResult = [];
|
||||
foreach ($aAvailableModules as $sId => $aModule) {
|
||||
$oModule = new MFModule($sId, $aModule['root_dir'], $aModule['label'], isset($aModule['auto_select']));
|
||||
|
||||
@@ -61,7 +61,7 @@ class DependencyExpression
|
||||
}
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
public static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
{
|
||||
if (!isset(self::$oPhpExpressionEvaluator)) {
|
||||
self::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
|
||||
@@ -96,7 +96,7 @@ class ModuleDiscovery
|
||||
protected static $m_aModuleVersionByName = [];
|
||||
|
||||
/** @var array<\iTopExtension> $m_aRemovedExtensions */
|
||||
protected static $m_aRemovedExtensions = [];
|
||||
protected static array $m_aRemovedExtensions = [];
|
||||
|
||||
// All the entries below are list of file paths relative to the module directory
|
||||
protected static $m_aFilesList = ['datamodel', 'webservice', 'dictionary', 'data.struct', 'data.sample'];
|
||||
@@ -196,21 +196,6 @@ class ModuleDiscovery
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of "discovered" modules, ordered based on their (inter) dependencies
|
||||
*
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array
|
||||
* @throws \MissingDependencyException
|
||||
*/
|
||||
protected static function GetModules($bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
// Order the modules to take into account their inter-dependencies
|
||||
return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrange an list of modules, based on their (inter) dependencies
|
||||
* @param array $aModules The list of modules to process: 'id' => $aModuleInfo
|
||||
@@ -238,6 +223,7 @@ class ModuleDiscovery
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aFilteredModules, $bAbortOnMissingDependency);
|
||||
}
|
||||
|
||||
@@ -245,7 +231,7 @@ class ModuleDiscovery
|
||||
* @param array<\iTopExtension> $aRemovedExtension
|
||||
* @return void
|
||||
*/
|
||||
public static function DeclareRemovedExtensions(array $aRemovedExtension)
|
||||
public static function DeclareRemovedExtensions(array $aRemovedExtension): void
|
||||
{
|
||||
if (self::$m_aRemovedExtensions != $aRemovedExtension) {
|
||||
self::ResetCache();
|
||||
@@ -253,79 +239,7 @@ class ModuleDiscovery
|
||||
self::$m_aRemovedExtensions = $aRemovedExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<\iTopExtension> $aExtensions
|
||||
* @param string $sModuleName
|
||||
* @param string $sModuleVersion
|
||||
* @param array $aModuleInfo
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function IsModuleInExtensionList(array $aExtensions, string $sModuleName, string $sModuleVersion, array $aModuleInfo): bool
|
||||
{
|
||||
if (count($aExtensions) === 0) {
|
||||
return false;
|
||||
}
|
||||
$aNonMatchingPaths = [];
|
||||
$sModuleFilePath = $aModuleInfo[ModuleFileReader::MODULE_FILE_PATH];
|
||||
|
||||
/** @var \iTopExtension $oExtension */
|
||||
foreach ($aExtensions as $oExtension) {
|
||||
$sCurrentVersion = $oExtension->aModuleVersion[$sModuleName] ?? null;
|
||||
if (is_null($sCurrentVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sModuleVersion !== $sCurrentVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aCurrentModuleInfo = $oExtension->aModuleInfo[$sModuleName] ?? null;
|
||||
if (is_null($aCurrentModuleInfo)) {
|
||||
SetupLog::Warning("Missing $sModuleName in ".$oExtension->sLabel.". it should not happen");
|
||||
continue;
|
||||
}
|
||||
|
||||
// use case: same module coming from 2 different extensions
|
||||
// we remove only the one coming from removed extensions
|
||||
$sCurrentModuleFilePath = $aCurrentModuleInfo[ModuleFileReader::MODULE_FILE_PATH];
|
||||
if (realpath($sModuleFilePath) !== realpath($sCurrentModuleFilePath)) {
|
||||
$aNonMatchingPaths[] = $sCurrentModuleFilePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupLog::Info("Module considered as removed", null, ['extension_code' => $oExtension->sCode, 'module_name' => $sModuleName, 'module_version' => $sModuleVersion, ModuleFileReader::MODULE_FILE_PATH => $sCurrentModuleFilePath]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($aNonMatchingPaths) > 0) {
|
||||
//add log for support
|
||||
SetupLog::Info("Module kept as it came from non removed extensions", null, ['module_name' => $sModuleName, 'module_version' => $sModuleVersion, ModuleFileReader::MODULE_FILE_PATH => $sModuleFilePath, 'non_matching_paths' => $aNonMatchingPaths]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
{
|
||||
if (!isset(self::$oPhpExpressionEvaluator)) {
|
||||
self::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
}
|
||||
|
||||
return self::$oPhpExpressionEvaluator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search (on the disk) for all defined iTop modules, load them and returns the list (as an array)
|
||||
* of the possible iTop modules to install
|
||||
*
|
||||
* @param $aSearchDirs array of directories to search (absolute paths)
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array A big array moduleID => ModuleData
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetAvailableModules($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
private static function Init($aSearchDirs): void
|
||||
{
|
||||
if (self::$m_aSearchDirs != $aSearchDirs) {
|
||||
self::ResetCache();
|
||||
@@ -344,13 +258,60 @@ class ModuleDiscovery
|
||||
clearstatcache();
|
||||
self::ListModuleFiles(basename($sSearchDir), dirname($sSearchDir));
|
||||
}
|
||||
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
|
||||
} else {
|
||||
// Reuse the previous results
|
||||
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all modules found on disk ordered by dependencies. Skipping modules coming from extensions declared as removed (@see ModuleDiscovery::DeclareRemovedExtensions)
|
||||
* @param $aSearchDirs array of directories to search (absolute paths)
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array A big array moduleID => ModuleData
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetModulesOrderedByDependencies($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
self::Init($aSearchDirs);
|
||||
|
||||
return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use \ModuleDiscovery::GetModulesOrderedByDependencies instead
|
||||
*/
|
||||
public static function GetAvailableModules($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
return ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all modules found on disk (without any dependency consideration). Skipping modules coming from extensions declared as removed (@see ModuleDiscovery::DeclareRemovedExtensions)
|
||||
*
|
||||
* @param $aSearchDirs array of directories to search (absolute paths)
|
||||
*
|
||||
* @return array A big array moduleID => ModuleData
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetAllModules($aSearchDirs)
|
||||
{
|
||||
self::Init($aSearchDirs);
|
||||
|
||||
$aNonRemovedModules = [];
|
||||
foreach (self::$m_aModules as $sModuleId => $aModuleInfo) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
|
||||
if (self::IsModuleInExtensionList(self::$m_aRemovedExtensions, $sModuleName, $oModule->GetVersion(), $aModuleInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aNonRemovedModules[$sModuleId] = $aModuleInfo;
|
||||
}
|
||||
|
||||
return $aNonRemovedModules;
|
||||
}
|
||||
|
||||
public static function ResetCache()
|
||||
{
|
||||
self::$m_aSearchDirs = null;
|
||||
@@ -419,6 +380,59 @@ class ModuleDiscovery
|
||||
throw new Exception("Data directory (".$sDirectory.") not found or not readable.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<\iTopExtension> $aExtensions
|
||||
* @param string $sModuleName
|
||||
* @param string $sModuleVersion
|
||||
* @param array $aModuleInfo
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function IsModuleInExtensionList(array $aExtensions, string $sModuleName, string $sModuleVersion, array $aModuleInfo): bool
|
||||
{
|
||||
if (count($aExtensions) === 0) {
|
||||
return false;
|
||||
}
|
||||
$aNonMatchingPaths = [];
|
||||
$sModuleFilePath = $aModuleInfo[ModuleFileReader::MODULE_FILE_PATH];
|
||||
|
||||
/** @var \iTopExtension $oExtension */
|
||||
foreach ($aExtensions as $oExtension) {
|
||||
$sCurrentVersion = $oExtension->aModuleVersion[$sModuleName] ?? null;
|
||||
if (is_null($sCurrentVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sModuleVersion !== $sCurrentVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aCurrentModuleInfo = $oExtension->aModuleInfo[$sModuleName] ?? null;
|
||||
if (is_null($aCurrentModuleInfo)) {
|
||||
SetupLog::Warning("Missing $sModuleName in ".$oExtension->sLabel.". it should not happen");
|
||||
continue;
|
||||
}
|
||||
|
||||
// use case: same module coming from 2 different extensions
|
||||
// we remove only the one coming from removed extensions
|
||||
$sCurrentModuleFilePath = $aCurrentModuleInfo[ModuleFileReader::MODULE_FILE_PATH];
|
||||
if (realpath($sModuleFilePath) !== realpath($sCurrentModuleFilePath)) {
|
||||
$aNonMatchingPaths[] = $sCurrentModuleFilePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupLog::Info("Module considered as removed", null, ['extension_code' => $oExtension->sCode, 'module_name' => $sModuleName, 'module_version' => $sModuleVersion, ModuleFileReader::MODULE_FILE_PATH => $sCurrentModuleFilePath]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($aNonMatchingPaths) > 0) {
|
||||
//add log for support
|
||||
SetupLog::Debug("Module kept as it came from non removed extensions", null, ['module_name' => $sModuleName, 'module_version' => $sModuleVersion, ModuleFileReader::MODULE_FILE_PATH => $sModuleFilePath, 'non_matching_paths' => $aNonMatchingPaths]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // End of class
|
||||
|
||||
/** Alias for backward compatibility with old module files in which
|
||||
|
||||
@@ -73,7 +73,7 @@ class AnalyzeInstallation
|
||||
//test only
|
||||
$aAvailableModules = $this->aAvailableModules;
|
||||
} else {
|
||||
$aAvailableModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
$aAvailableModules = ModuleDiscovery::GetModulesOrderedByDependencies($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
foreach ($aAvailableModules as $sModuleId => $aModuleInfo) {
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\DependencyExpression;
|
||||
|
||||
require_once __DIR__.'/ModuleInstallationException.php';
|
||||
require_once(APPROOT.'/setup/moduledependency/module.class.inc.php');
|
||||
|
||||
class InstallationChoicesToModuleConverter
|
||||
{
|
||||
private static ?InstallationChoicesToModuleConverter $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): InstallationChoicesToModuleConverter
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new InstallationChoicesToModuleConverter();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?InstallationChoicesToModuleConverter $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aInstallationChoices
|
||||
* @param array $aSearchDirs
|
||||
*
|
||||
* @return array
|
||||
* @throws \ModuleInstallationException
|
||||
*/
|
||||
public function GetModules(array $aInstallationChoices, array $aSearchDirs, ?string $sInstallationFilePath = null): array
|
||||
{
|
||||
$aPackageModules = ModuleDiscovery::GetAllModules($aSearchDirs);
|
||||
|
||||
$bInstallationFileProvided = ! is_null($sInstallationFilePath) && is_file($sInstallationFilePath);
|
||||
|
||||
if ($bInstallationFileProvided) {
|
||||
$oXMLParameters = new XMLParameters($sInstallationFilePath);
|
||||
$aSteps = $oXMLParameters->Get('steps', []);
|
||||
if (!is_array($aSteps)) {
|
||||
return [];
|
||||
}
|
||||
$aInstalledModuleNames = $this->FindInstalledPackageModules($aPackageModules, $aInstallationChoices, $aSteps);
|
||||
} else {
|
||||
$aInstalledModuleNames = $this->FindInstalledPackageModules($aPackageModules, $aInstallationChoices);
|
||||
}
|
||||
|
||||
$aInstalledModules = [];
|
||||
foreach (array_keys($aPackageModules) as $sModuleId) {
|
||||
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (in_array($sModuleName, $aInstalledModuleNames)) {
|
||||
$aInstalledModules[] = $sModuleId;
|
||||
}
|
||||
}
|
||||
|
||||
return $aInstalledModules;
|
||||
}
|
||||
|
||||
private function FindInstalledPackageModules(array $aPackageModules, array $aInstallationChoices, array $aInstallationDescription = null): array
|
||||
{
|
||||
$aInstalledModules = [];
|
||||
|
||||
$this->ProcessDefaultModules($aPackageModules, $aInstalledModules);
|
||||
|
||||
if (is_null($aInstallationDescription)) {
|
||||
//in legacy usecase: choices are flat modules list already
|
||||
foreach ($aInstallationChoices as $sModuleName) {
|
||||
$aInstalledModules[$sModuleName] = true;
|
||||
}
|
||||
} else {
|
||||
$this->GetModuleNamesFromInstallationChoices($aInstallationChoices, $aInstallationDescription, $aInstalledModules);
|
||||
}
|
||||
|
||||
$this->ProcessAutoSelectModules($aPackageModules, $aInstalledModules);
|
||||
|
||||
return array_keys($aInstalledModules);
|
||||
}
|
||||
|
||||
private function IsDefaultModule(string $sModuleId, array $aModule): bool
|
||||
{
|
||||
if (($sModuleId === ROOT_MODULE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($aModule['auto_select'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($aModule['category'] === 'authentication') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !$aModule['visible'];
|
||||
}
|
||||
|
||||
private function ProcessDefaultModules(array &$aPackageModules, array &$aInstalledModules): void
|
||||
{
|
||||
foreach ($aPackageModules as $sModuleId => $aModule) {
|
||||
if ($this->IsDefaultModule($sModuleId, $aModule)) {
|
||||
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
$aInstalledModules[$sModuleName] = true;
|
||||
unset($aPackageModules[$sModuleId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function IsAutoSelectedModule(array $aInstalledModules, string $sModuleId, array $aModule): bool
|
||||
{
|
||||
if (($sModuleId === ROOT_MODULE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($aModule['auto_select'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
SetupInfo::SetSelectedModules($aInstalledModules);
|
||||
return DependencyExpression::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($aModule['auto_select']);
|
||||
} catch (Exception $e) {
|
||||
IssueLog::Error('Error evaluating module auto-select', null, [
|
||||
'module' => $sModuleId,
|
||||
'error' => $e->getMessage(),
|
||||
'evaluated code' => $aModule['auto_select'],
|
||||
'stacktrace' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function ProcessAutoSelectModules(array $aPackageModules, array &$aInstalledModules): void
|
||||
{
|
||||
foreach ($aPackageModules as $sModuleId => $aModule) {
|
||||
if ($this->IsAutoSelectedModule($aInstalledModules, $sModuleId, $aModule)) {
|
||||
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
$aInstalledModules[$sModuleName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function GetModuleNamesFromInstallationChoices(array $aInstallationChoices, array $aInstallationDescription, array &$aModuleNames): void
|
||||
{
|
||||
foreach ($aInstallationDescription as $aStepInfo) {
|
||||
$aOptions = $aStepInfo['options'] ?? null;
|
||||
if (is_array($aOptions)) {
|
||||
foreach ($aOptions as $aChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aChoiceInfo, $aModuleNames);
|
||||
}
|
||||
}
|
||||
$aOptions = $aStepInfo['alternatives'] ?? null;
|
||||
if (is_array($aOptions)) {
|
||||
foreach ($aOptions as $aChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aChoiceInfo, $aModuleNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function ProcessSelectedChoice(array $aInstallationChoices, array $aChoiceInfo, array &$aInstalledModules)
|
||||
{
|
||||
if (!is_array($aChoiceInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sMandatory = $aChoiceInfo['mandatory'] ?? 'false';
|
||||
|
||||
$aCurrentModules = $aChoiceInfo['modules'] ?? [];
|
||||
$sExtensionCode = $aChoiceInfo['extension_code'];
|
||||
|
||||
$bSelected = ($sMandatory === 'true') || in_array($sExtensionCode, $aInstallationChoices);
|
||||
|
||||
if (!$bSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($aCurrentModules as $sModuleId) {
|
||||
$aInstalledModules[$sModuleId] = true;
|
||||
}
|
||||
|
||||
$aAlternatives = $aChoiceInfo['alternatives'] ?? null;
|
||||
if (is_array($aAlternatives)) {
|
||||
foreach ($aAlternatives as $aSubChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
|
||||
}
|
||||
}
|
||||
|
||||
$aSubOptionsChoiceInfo = $aChoiceInfo['sub_options'] ?? null;
|
||||
if (is_array($aSubOptionsChoiceInfo)) {
|
||||
$aSubOptions = $aSubOptionsChoiceInfo['options'] ?? null;
|
||||
if (is_array($aSubOptions)) {
|
||||
foreach ($aSubOptions as $aSubChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
|
||||
}
|
||||
}
|
||||
$aSubAlternatives = $aSubOptionsChoiceInfo['alternatives'] ?? null;
|
||||
if (is_array($aSubAlternatives)) {
|
||||
foreach ($aSubAlternatives as $aSubChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
setup/moduleinstallation/ModuleInstallationException.php
Normal file
5
setup/moduleinstallation/ModuleInstallationException.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class ModuleInstallationException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -32,7 +32,8 @@ 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.'setup/AnalyzeInstallation.php';
|
||||
require_once APPROOT.'setup/moduleinstallation/AnalyzeInstallation.php';
|
||||
require_once APPROOT . '/setup/moduleinstallation/InstallationChoicesToModuleConverter.php';
|
||||
|
||||
define('MODULE_ACTION_OPTIONAL', 1);
|
||||
define('MODULE_ACTION_MANDATORY', 2);
|
||||
@@ -129,7 +130,7 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false): void
|
||||
{
|
||||
require_once APPROOT.'/setup/moduleinstallation.class.inc.php';
|
||||
require_once APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php';
|
||||
|
||||
$sConfigFile = $oConfig->GetLoadedFile();
|
||||
if (strlen($sConfigFile) > 0) {
|
||||
@@ -225,12 +226,29 @@ class RunTimeEnvironment
|
||||
return ($oExtension->sSource == iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
|
||||
public function GetExtraDirsToCompile(string $sSourceDir) : array {
|
||||
$sSourceDirFull = APPROOT.$sSourceDir;
|
||||
if (!is_dir($sSourceDirFull)) {
|
||||
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
|
||||
}
|
||||
$aDirsToCompile = [$sSourceDirFull];
|
||||
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToCompile[] = APPROOT.'extensions';
|
||||
}
|
||||
$sExtraDir = utils::GetDataPath().$this->sTargetEnv.'-modules/';
|
||||
if (is_dir($sExtraDir)) {
|
||||
$aDirsToCompile[] = $sExtraDir;
|
||||
}
|
||||
|
||||
return $aDirsToCompile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the installed modules (only the installed ones)
|
||||
*/
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
{
|
||||
\SetupLog::Info(__METHOD__);
|
||||
$sSourceDirFull = APPROOT.$sSourceDir;
|
||||
if (!is_dir($sSourceDirFull)) {
|
||||
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
|
||||
|
||||
@@ -509,7 +509,7 @@ class SetupUtils
|
||||
}
|
||||
require_once(APPROOT.'setup/modulediscovery.class.inc.php');
|
||||
try {
|
||||
ModuleDiscovery::GetAvailableModules($aDirsToScan, true, $aSelectedModules);
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($aDirsToScan, true, $aSelectedModules);
|
||||
} catch (Exception $e) {
|
||||
$aResult[] = new CheckResult(CheckResult::ERROR, $e->getMessage());
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ class InstallationFileService
|
||||
{
|
||||
$sProductionModuleDir = APPROOT.'data/'.$this->sTargetEnvironment.'-modules/';
|
||||
|
||||
$aAvailableModules = $this->GetProductionEnv()->AnalyzeInstallation(MetaModel::GetConfig(), $this->GetExtraDirs(), false, null);
|
||||
$aAvailableModules = $this->GetProductionEnv()->AnalyzeInstallation(MetaModel::GetConfig(), $this->GetExtraDirs());
|
||||
|
||||
$this->aAutoSelectModules = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
|
||||
@@ -1325,6 +1325,8 @@ class WizStepModulesChoice extends WizardStep
|
||||
*/
|
||||
protected iTopExtensionsMap $oExtensionsMap;
|
||||
|
||||
private ?array $aSteps = null;
|
||||
|
||||
protected PhpExpressionEvaluator $oPhpExpressionEvaluator;
|
||||
|
||||
/**
|
||||
@@ -1370,11 +1372,11 @@ class WizStepModulesChoice extends WizardStep
|
||||
}
|
||||
}
|
||||
|
||||
public function GetTitle()
|
||||
public function GetTitle(): string
|
||||
{
|
||||
$aStepInfo = $this->GetStepInfo();
|
||||
$sTitle = isset($aStepInfo['title']) ? $aStepInfo['title'] : 'Modules selection';
|
||||
return $sTitle;
|
||||
|
||||
return $aStepInfo['title'] ?? 'Modules selection';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
@@ -1889,104 +1891,47 @@ EOF
|
||||
|
||||
protected function GetStepInfo($idx = null)
|
||||
{
|
||||
$aStepInfo = null;
|
||||
if ($idx === null) {
|
||||
$index = $this->GetStepIndex();
|
||||
} else {
|
||||
$index = $idx;
|
||||
}
|
||||
$index = $idx ?? $this->GetStepIndex();
|
||||
|
||||
$aSteps = [];
|
||||
$this->oWizard->SetParameter('additional_extensions_modules', json_encode([])); // Default value, no additional extensions
|
||||
if (is_null($this->aSteps)) {
|
||||
$this->oWizard->SetParameter('additional_extensions_modules', json_encode([])); // Default value, no additional extensions
|
||||
|
||||
if (@file_exists($this->GetSourceFilePath())) {
|
||||
// Found an "installation.xml" file, let's use this definition for the wizard
|
||||
$aParams = new XMLParameters($this->GetSourceFilePath());
|
||||
$aSteps = $aParams->Get('steps', []);
|
||||
if (@file_exists($this->GetSourceFilePath())) {
|
||||
// Found an "installation.xml" file, let's use this definition for the wizard
|
||||
$aParams = new XMLParameters($this->GetSourceFilePath());
|
||||
$this->aSteps = $aParams->Get('steps', []);
|
||||
|
||||
// Additional step for the "extensions"
|
||||
$aStepDefinition = [
|
||||
'title' => 'Extensions',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.</h2>',
|
||||
'banner' => '/images/icons/icons8-puzzle.svg',
|
||||
'options' => [],
|
||||
];
|
||||
if ($index + 1 >= count($this->aSteps)) {
|
||||
//make sure we also cache next step as well
|
||||
$aOptions = $this->oExtensionsMap->GetAllExtensionsOptionInfo();
|
||||
|
||||
foreach ($this->oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
if (($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) && ($oExtension->bVisible) && (count($oExtension->aMissingDependencies) == 0)) {
|
||||
$aStepDefinition['options'][] = [
|
||||
'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),
|
||||
'uninstallable' => $oExtension->CanBeUninstalled(),
|
||||
'missing' => $oExtension->bRemovedFromDisk,
|
||||
];
|
||||
// Display this step of the wizard only if there is something to display
|
||||
if (count($aOptions) > 0) {
|
||||
$this->aSteps[] = [
|
||||
'title' => 'Extensions',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.</h2>',
|
||||
'banner' => '/images/icons/icons8-puzzle.svg',
|
||||
'options' => $aOptions,
|
||||
];
|
||||
$this->oWizard->SetParameter('additional_extensions_modules', json_encode($aOptions));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$aOptions = $this->oExtensionsMap->GetAllExtensionsOptionInfo();
|
||||
|
||||
// No wizard configuration provided, build a standard one with just one big list. All items are mandatory, only works when there are no conflicted modules.
|
||||
$this->aSteps = [
|
||||
[
|
||||
'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/icons/icons8-apps-tab.svg',
|
||||
'options' => $aOptions,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// No wizard configuration provided, build a standard one with just one big list. All items are mandatory, only works when there are no conflicted modules.
|
||||
$aStepDefinition = [
|
||||
'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/icons/icons8-apps-tab.svg',
|
||||
'options' => [],
|
||||
];
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if (($oExtension->bVisible) && (count($oExtension->aMissingDependencies) == 0)) {
|
||||
$aStepDefinition['options'][] = [
|
||||
'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)) {
|
||||
$aStepInfo = $aSteps[$index];
|
||||
}
|
||||
|
||||
return $aStepInfo;
|
||||
}
|
||||
|
||||
protected function GetExtensionSourceLabel($sSource)
|
||||
{
|
||||
$sDecorationClass = '';
|
||||
switch ($sSource) {
|
||||
case iTopExtension::SOURCE_MANUAL:
|
||||
$sResult = 'Local extensions folder';
|
||||
$sDecorationClass = 'fas fa-folder';
|
||||
break;
|
||||
|
||||
case iTopExtension::SOURCE_REMOTE:
|
||||
$sResult = (ITOP_APPLICATION == 'iTop') ? 'iTop Hub' : 'ITSM Designer';
|
||||
$sDecorationClass = (ITOP_APPLICATION == 'iTop') ? 'fc fc-chameleon-icon' : 'fa pencil-ruler';
|
||||
break;
|
||||
|
||||
default:
|
||||
$sResult = '';
|
||||
}
|
||||
if ($sResult == '') {
|
||||
return '';
|
||||
}
|
||||
return '<i class="setup-extension--icon '.$sDecorationClass.'" data-tooltip-content="'.$sResult.'"></i>';
|
||||
return $this->aSteps[$index] ?? null;
|
||||
}
|
||||
|
||||
public function ComputeChoiceFlags(array $aChoice, string $sChoiceId, array $aSelectedComponents, bool $bAllDisabled, bool $bDisableUninstallCheck, bool $bUpgradeMode)
|
||||
@@ -2042,17 +1987,17 @@ EOF
|
||||
$sTooltip = '';
|
||||
$sUnremovable = '';
|
||||
if ($aFlags['missing']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag removed">source removed</span>';
|
||||
$sTooltip .= '<div class="setup-extension-tag removed">source removed</div>';
|
||||
}
|
||||
if ($aFlags['installed']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag checked installed">installed</span>';
|
||||
$sTooltip .= '<span class="setup-extension-tag unchecked tobeuninstalled">to be uninstalled</span>';
|
||||
$sTooltip .= '<div class="setup-extension-tag checked installed">installed</div>';
|
||||
$sTooltip .= '<div class="setup-extension-tag unchecked tobeuninstalled">to be uninstalled</div>';
|
||||
} else {
|
||||
$sTooltip .= '<span class="setup-extension-tag checked tobeinstalled">to be installed</span>';
|
||||
$sTooltip .= '<span class="setup-extension-tag unchecked notinstalled">not installed</span>';
|
||||
$sTooltip .= '<div class="setup-extension-tag checked tobeinstalled">to be installed</div>';
|
||||
$sTooltip .= '<div class="setup-extension-tag unchecked notinstalled">not installed</div>';
|
||||
}
|
||||
if (!$aFlags['uninstallable']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag notuninstallable">cannot be uninstalled</span>';
|
||||
$sTooltip .= '<div class="setup-extension-tag notuninstallable">cannot be uninstalled</div>';
|
||||
}
|
||||
if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) {
|
||||
$this->bCanMoveForward = false;//Disable "Next"
|
||||
@@ -2245,11 +2190,10 @@ class WizStepSummary extends WizardStep
|
||||
$oPage->add('<div class="closed"><span class="title ibo-setup-summary-title">Extensions to be installed</span>');
|
||||
$aExtensionsAdded = json_decode($this->oWizard->GetParameter('extensions_added'), true);
|
||||
|
||||
$sExtensionsAdded = '';
|
||||
if (count($aExtensionsAdded)) {
|
||||
if (count($aExtensionsAdded) > 0) {
|
||||
$sExtensionsAdded = '<ul>';
|
||||
foreach ($aExtensionsAdded as $sExtensionCode => $sLabel) {
|
||||
$sExtensionsAdded .= '<li>'.$sLabel.'</li>';
|
||||
$sExtensionsAdded .= "<li>$sLabel</li>'";
|
||||
}
|
||||
$sExtensionsAdded .= '</ul>';
|
||||
} else {
|
||||
@@ -2261,15 +2205,14 @@ class WizStepSummary extends WizardStep
|
||||
|
||||
$aExtensionsRemoved = json_decode($this->oWizard->GetParameter('removed_extensions'), true) ?? [];
|
||||
$aExtensionsNotUninstallable = json_decode($this->oWizard->GetParameter('extensions_not_uninstallable'));
|
||||
$sExtensionsRemoved = '';
|
||||
if (count($aExtensionsRemoved) > 0) {
|
||||
$sExtensionsRemoved = '<ul>';
|
||||
foreach ($aExtensionsRemoved as $sExtensionCode => $sLabel) {
|
||||
$sForcedUninstall = '';
|
||||
if (in_array($sExtensionCode, $aExtensionsNotUninstallable)) {
|
||||
$sForcedUninstall = ' (forced uninstallation)';
|
||||
$sExtensionsRemoved .= "<li>$sLabel (forced uninstallation)</li>";
|
||||
} else {
|
||||
$sExtensionsRemoved .= "<li>$sLabel</li>";
|
||||
}
|
||||
$sExtensionsRemoved .= '<li>'.$sLabel.$sForcedUninstall.'</li>';
|
||||
}
|
||||
$sExtensionsRemoved .= '</ul>';
|
||||
} else {
|
||||
@@ -2331,8 +2274,6 @@ class WizStepSummary extends WizardStep
|
||||
|
||||
}
|
||||
|
||||
$aSelectedModules = $aInstallParams['selected_modules'];
|
||||
|
||||
if (isset($aMiscOptions['generate_config'])) {
|
||||
$oDoc = new DOMDocument('1.0', 'UTF-8');
|
||||
$oDoc->preserveWhiteSpace = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
includes:
|
||||
- php-includes/set-php-version-from-process.php # Workaround to set PHP version to the on running the CLI
|
||||
# for an explanation of the baseline concept, see: https://phpstan.org/user-guide/baseline
|
||||
#baseline HERE DO NOT REMOVE FOR CI
|
||||
#baseline HERE DO NOT REMOVE FOR CI
|
||||
|
||||
parameters:
|
||||
level: 0
|
||||
|
||||
@@ -188,6 +188,9 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
CMDBSource::DropTable("priv_module_install");
|
||||
CMDBSource::Query("CREATE TABLE $sNewDB.priv_module_install SELECT * FROM $sPreviousDB.priv_module_install");
|
||||
|
||||
CMDBSource::DropTable("priv_extension_install");
|
||||
CMDBSource::Query("CREATE TABLE $sNewDB.priv_extension_install SELECT * FROM $sPreviousDB.priv_extension_install");
|
||||
|
||||
$this->debug("Custom environment '$sTestEnv' is ready!");
|
||||
} else {
|
||||
$this->debug("Custom environment '$sTestEnv' READY BUILT:");
|
||||
|
||||
@@ -498,6 +498,34 @@ class MetaModelTest extends ItopDataTestCase
|
||||
'Purge 10 items with a max_chunk_size of 1000 (default value) should be perfomed in 1 step' => [1000, 3],
|
||||
];
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_UnknownClass()
|
||||
{
|
||||
$this->expectExceptionMessage("Cannot find class module");
|
||||
$this->expectException(CoreException::class);
|
||||
|
||||
MetaModel::GetModuleName('GABUZOMEU');
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromCorePhpFile()
|
||||
{
|
||||
$this->assertEquals('core', MetaModel::GetModuleName('BackgroundTask'));
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromCorePhpFile2()
|
||||
{
|
||||
$this->assertEquals('core', MetaModel::GetModuleName('lnkActionNotificationToContact'));
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromModulePhpFile()
|
||||
{
|
||||
$this->assertEquals('itop-attachments', MetaModel::GetModuleName('CMDBChangeOpAttachmentAdded'));
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromXmlDataModelFile()
|
||||
{
|
||||
$this->assertEquals('authent-ldap', MetaModel::GetModuleName('UserLDAP'));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Wizzard
|
||||
|
||||
@@ -4,7 +4,8 @@ class WizStepModulesChoiceFake extends WizStepModulesChoice
|
||||
{
|
||||
public function __construct(WizardController $oWizard, $sCurrentState)
|
||||
{
|
||||
|
||||
$this->oWizard = $oWizard;
|
||||
$this->sCurrentState = $sCurrentState;
|
||||
}
|
||||
|
||||
public function setExtensionMap(iTopExtensionsMap $oMap)
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use ItopExtensionsMap;
|
||||
use iTopExtensionsMap;
|
||||
use iTopExtensionsMapFake;
|
||||
use ModuleDiscovery;
|
||||
use WizardController;
|
||||
use WizStepModulesChoiceFake;
|
||||
use XMLParameters;
|
||||
|
||||
class WizStepModulesChoiceTest extends ItopTestCase
|
||||
{
|
||||
private WizStepModulesChoiceFake $oStep;
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -17,7 +20,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
|
||||
require_once __DIR__.'/iTopExtensionsMapFake.php';
|
||||
require_once __DIR__.'/WizStepModulesChoiceFake.php';
|
||||
|
||||
$this->oStep = new \WizStepModulesChoiceFake(new WizardController('', ''), '');
|
||||
$this->oStep = new WizStepModulesChoiceFake(new WizardController('', ''), '');
|
||||
ModuleDiscovery::ResetCache();
|
||||
}
|
||||
|
||||
@@ -350,4 +353,156 @@ class WizStepModulesChoiceTest extends ItopTestCase
|
||||
$this->assertEquals($aExpectedRemovedList, $aRemovedList);
|
||||
}
|
||||
|
||||
public function testGetStepInfo_PackageWithoutInstallationXML()
|
||||
{
|
||||
|
||||
$aExtensionsOnDiskOrDb = self::GivenExtensionsOnDisk();
|
||||
$oWizStepModulesChoice = $this->GivenWizStepModulesChoiceWithoutXmlInstallation($aExtensionsOnDiskOrDb);
|
||||
|
||||
$expected = [
|
||||
'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/icons/icons8-apps-tab.svg',
|
||||
'options' => $aExtensionsOnDiskOrDb,
|
||||
];
|
||||
|
||||
$this->CallAndCheckTwice($oWizStepModulesChoice, null, $expected);
|
||||
$this->CallAndCheckTwice($oWizStepModulesChoice, 1, null);
|
||||
}
|
||||
|
||||
private function GivenWizStepModulesChoiceWithoutXmlInstallation(array $aExtensionsOnDiskOrDb): WizStepModulesChoiceFake
|
||||
{
|
||||
$oExtensionsMap = $this->createMock(iTopExtensionsMap::class);
|
||||
$oExtensionsMap->expects($this->once())
|
||||
->method('GetAllExtensionsOptionInfo')
|
||||
->willReturn($aExtensionsOnDiskOrDb);
|
||||
|
||||
$oWizard = new WizardController('', '');
|
||||
$oWizStepModulesChoice = new WizStepModulesChoiceFake($oWizard, '');
|
||||
$oWizStepModulesChoice->setExtensionMap($oExtensionsMap);
|
||||
|
||||
return $oWizStepModulesChoice;
|
||||
}
|
||||
|
||||
public static function PackageWithInstallationXMLProvider()
|
||||
{
|
||||
require_once __DIR__.'/../../../../approot.inc.php';
|
||||
require_once APPROOT.'setup/parameters.class.inc.php';
|
||||
|
||||
$aUsecases = [];
|
||||
|
||||
$aUsecases["[no step] with extensions"] = [
|
||||
'iGetStepInfoIdxArg' => null,
|
||||
'expected' => self::GetStep(0),
|
||||
];
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$aUsecases["[step $i] with extensions"] = [
|
||||
'iGetStepInfoIdxArg' => $i,
|
||||
'expected' => self::GetStep($i),
|
||||
];
|
||||
}
|
||||
|
||||
$aUsecases["[step 6] with extensions => NO STEP ANYMORE"] = [
|
||||
'iGetStepInfoIdxArg' => 6,
|
||||
'expected' => null,
|
||||
'iGetAllExtensionsOptionInfoCallCount' => 1,
|
||||
];
|
||||
|
||||
return $aUsecases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider PackageWithInstallationXMLProvider
|
||||
*/
|
||||
public function testGetStepInfo_PackageWithInstallationXMLWithExtensions($iGetStepInfoIdxArg, $expected, $iGetAllExtensionsOptionInfoCallCount = 0)
|
||||
{
|
||||
$aExtensionsOnDiskOrDb = self::GivenExtensionsOnDisk();
|
||||
$oWizStepModulesChoice = $this->GivenWizStepModulesChoiceWithXmlInstallation($aExtensionsOnDiskOrDb, $iGetAllExtensionsOptionInfoCallCount);
|
||||
|
||||
$this->CallAndCheckTwice($oWizStepModulesChoice, $iGetStepInfoIdxArg, $expected);
|
||||
}
|
||||
|
||||
public function testGetStepInfo_PackageWithInstallationXML_AfterLastStepWithExtensions()
|
||||
{
|
||||
$expected = [
|
||||
'title' => 'Extensions',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.</h2>',
|
||||
'banner' => '/images/icons/icons8-puzzle.svg',
|
||||
'options' => self::GivenExtensionsOnDisk(),
|
||||
];
|
||||
|
||||
$aExtensionsOnDiskOrDb = self::GivenExtensionsOnDisk();
|
||||
$oWizStepModulesChoice = $this->GivenWizStepModulesChoiceWithXmlInstallation($aExtensionsOnDiskOrDb, 1);
|
||||
|
||||
$this->CallAndCheckTwice($oWizStepModulesChoice, 5, $expected);
|
||||
}
|
||||
|
||||
public function testGetStepInfo_PackageWithInstallationXMLAfterLastStepWithoutExtensions()
|
||||
{
|
||||
$oWizStepModulesChoice = $this->GivenWizStepModulesChoiceWithXmlInstallation([], 1);
|
||||
|
||||
$this->CallAndCheckTwice($oWizStepModulesChoice, 5, null);
|
||||
}
|
||||
|
||||
public function testGetStepInfo_PackageWithInstallationXML_MakeSureNextStepIsAlsoCached()
|
||||
{
|
||||
$aExtensionsOnDiskOrDb = self::GivenExtensionsOnDisk();
|
||||
$oWizStepModulesChoice = $this->GivenWizStepModulesChoiceWithXmlInstallation($aExtensionsOnDiskOrDb, 1);
|
||||
|
||||
$this->CallAndCheckTwice($oWizStepModulesChoice, 4, self::GetStep(4));
|
||||
|
||||
$expected = [
|
||||
'title' => 'Extensions',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.</h2>',
|
||||
'banner' => '/images/icons/icons8-puzzle.svg',
|
||||
'options' => $aExtensionsOnDiskOrDb,
|
||||
];
|
||||
$this->CallAndCheckTwice($oWizStepModulesChoice, 5, $expected);
|
||||
}
|
||||
|
||||
private static function GivenExtensionsOnDisk(): array
|
||||
{
|
||||
return [
|
||||
'itop-ext-added1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext-added2' => [
|
||||
'installed' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function GivenWizStepModulesChoiceWithXmlInstallation(array $aExtensionsOnDiskOrDb, $iGetAllExtensionsOptionInfoCallCount): WizStepModulesChoiceFake
|
||||
{
|
||||
$oExtensionsMap = $this->createMock(iTopExtensionsMap::class);
|
||||
$oExtensionsMap->expects($this->exactly($iGetAllExtensionsOptionInfoCallCount))
|
||||
->method('GetAllExtensionsOptionInfo')
|
||||
->willReturn($aExtensionsOnDiskOrDb);
|
||||
|
||||
$oWizard = new WizardController('', '');
|
||||
//needed to find installation.xml
|
||||
$oWizard->SetParameter('source_dir', __DIR__.'/ressources');
|
||||
$oWizStepModulesChoice = new WizStepModulesChoiceFake($oWizard, '');
|
||||
$oWizStepModulesChoice->setExtensionMap($oExtensionsMap);
|
||||
|
||||
return $oWizStepModulesChoice;
|
||||
}
|
||||
|
||||
private function CallAndCheckTwice($oStep, $iGetStepInfoIdxArg, $expected)
|
||||
{
|
||||
$aRes = $this->InvokeNonPublicMethod(WizStepModulesChoiceFake::class, 'GetStepInfo', $oStep, [$iGetStepInfoIdxArg]);
|
||||
$this->assertEquals($expected, $aRes, "step:".$iGetStepInfoIdxArg);
|
||||
|
||||
$aRes = $this->InvokeNonPublicMethod(WizStepModulesChoiceFake::class, 'GetStepInfo', $oStep, [$iGetStepInfoIdxArg]);
|
||||
$this->assertEquals($expected, $aRes, "(2nd call) step:".$iGetStepInfoIdxArg);
|
||||
}
|
||||
|
||||
private static function GetStep($index)
|
||||
{
|
||||
$aParams = new XMLParameters(__DIR__.'/ressources/installation.xml');
|
||||
$aSteps = $aParams->Get('steps', []);
|
||||
|
||||
return $aSteps[$index] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class iTopExtensionTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('/setup/unattended-install/InstallationFileService.php');
|
||||
ModuleDiscovery::ResetCache();
|
||||
}
|
||||
|
||||
public function testCanBeUninstalledDefaultValueIsTrue()
|
||||
{
|
||||
$oExtension = new iTopExtension();
|
||||
$this->assertTrue($oExtension->CanBeUninstalled(), 'An extension should be uninstallable by default.');
|
||||
}
|
||||
|
||||
public function testCanBeUninstalledReturnTrueWhenAllModulesCanBeUninstalled()
|
||||
{
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->aModuleInfo['combodo-test-yes1'] = [
|
||||
'uninstallable' => 'yes',
|
||||
];
|
||||
$oExtension->aModuleInfo['combodo-test-yes2'] = [
|
||||
'uninstallable' => 'yes',
|
||||
];
|
||||
$this->assertTrue($oExtension->CanBeUninstalled(), 'An extension should be considered uninstallable if all of its modules are uninstallable.');
|
||||
}
|
||||
|
||||
public function testCanBeUninstalledReturnFalseWhenAtLeastOneModuleCannotBeUninstalled()
|
||||
{
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->aModuleInfo['combodo-test-yes'] = [
|
||||
'uninstallable' => 'yes',
|
||||
];
|
||||
$oExtension->aModuleInfo['combodo-test-no'] = [
|
||||
'uninstallable' => 'no',
|
||||
];
|
||||
$this->assertFalse($oExtension->CanBeUninstalled(), 'An extension should be considered non-uninstallable if at least one of its modules is not uninstallable.');
|
||||
}
|
||||
|
||||
public function testCanBeUninstalledAnyValueDifferentThanYesIsConsideredFalse()
|
||||
{
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->aModuleInfo['combodo-test-maybe'] = [
|
||||
'uninstallable' => 'maybe',
|
||||
];
|
||||
$this->assertFalse($oExtension->CanBeUninstalled(), 'Any value in the uninstallable flag different than yes should be considered false.');
|
||||
}
|
||||
|
||||
public function testCanBeUninstalledExtensionValueOverwriteModulesValue()
|
||||
{
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->bCanBeUninstalled = true;
|
||||
$oExtension->aModuleInfo['combodo-test-no'] = [
|
||||
'uninstallable' => 'no',
|
||||
];
|
||||
$this->assertTrue($oExtension->CanBeUninstalled(), 'The uninstallable flag provided in the extension should prevail over those defined in the modules.');
|
||||
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->bCanBeUninstalled = false;
|
||||
$oExtension->aModuleInfo['combodo-test-yes'] = [
|
||||
'uninstallable' => 'yes',
|
||||
];
|
||||
$this->assertFalse($oExtension->CanBeUninstalled(), 'The uninstallable flag provided in the extension should prevail over those defined in the modules.');
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@ class AnalyzeInstallationTest extends ItopTestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/AnalyzeInstallation.php');
|
||||
$this->RequireOnceItopFile('setup/ModuleInstallationRepository.php');
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/runtimeenv.class.inc.php');
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class InstallationChoicesToModuleConverterTest extends ItopDataTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->RequireOnceItopFile('/setup/moduleinstallation/InstallationChoicesToModuleConverter.php');
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
ModuleDiscovery::ResetCache();
|
||||
}
|
||||
|
||||
//integration test
|
||||
public function testGetModulesWithXmlInstallationFile_UsualCustomerPackagesWithNonITIL()
|
||||
{
|
||||
$aSearchDirs = $this->GivenModuleDiscoveryInit();
|
||||
|
||||
$aInstalledModules = InstallationChoicesToModuleConverter::GetInstance()->GetModules(
|
||||
$this->GivenNonItilChoices(),
|
||||
$aSearchDirs,
|
||||
__DIR__.'/ressources/installation.xml'
|
||||
);
|
||||
|
||||
$aExpected = [
|
||||
'authent-cas/3.3.0',
|
||||
'authent-external/3.3.0',
|
||||
'authent-ldap/3.3.0',
|
||||
'authent-local/3.3.0',
|
||||
'combodo-backoffice-darkmoon-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-high-contrast-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-protanopia-deuteranopia-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-tritanopia-theme/3.3.0',
|
||||
'itop-attachments/3.3.0',
|
||||
'itop-backup/3.3.0',
|
||||
'itop-config/3.3.0',
|
||||
'itop-files-information/3.3.0',
|
||||
'itop-portal-base/3.3.0',
|
||||
'itop-portal/3.3.0',
|
||||
'itop-profiles-itil/3.3.0',
|
||||
'itop-sla-computation/3.3.0',
|
||||
'itop-structure/3.3.0',
|
||||
'itop-themes-compat/3.3.0',
|
||||
'itop-tickets/3.3.0',
|
||||
'itop-welcome-itil/3.3.0',
|
||||
'combodo-db-tools/3.3.0',
|
||||
'itop-config-mgmt/3.3.0',
|
||||
'itop-core-update/3.3.0',
|
||||
'itop-datacenter-mgmt/3.3.0',
|
||||
'itop-endusers-devices/3.3.0',
|
||||
'itop-faq-light/3.3.0',
|
||||
'itop-hub-connector/3.3.0',
|
||||
'itop-knownerror-mgmt/3.3.0',
|
||||
'itop-oauth-client/3.3.0',
|
||||
'itop-request-mgmt/3.3.0',
|
||||
'itop-service-mgmt/3.3.0',
|
||||
'itop-storage-mgmt/3.3.0',
|
||||
'itop-virtualization-mgmt/3.3.0',
|
||||
'itop-bridge-cmdb-services/3.3.0',
|
||||
'itop-bridge-cmdb-ticket/3.3.0',
|
||||
'itop-bridge-datacenter-mgmt-services/3.3.0',
|
||||
'itop-bridge-endusers-devices-services/3.3.0',
|
||||
'itop-bridge-storage-mgmt-services/3.3.0',
|
||||
'itop-bridge-virtualization-mgmt-services/3.3.0',
|
||||
'itop-bridge-virtualization-storage/3.3.0',
|
||||
'itop-change-mgmt/3.3.0',
|
||||
];
|
||||
$this->assertEquals($aExpected, $aInstalledModules);
|
||||
}
|
||||
|
||||
//integration test
|
||||
public function testGetModulesWithXmlInstallationFile_UsualCustomerPackagesWithITIL()
|
||||
{
|
||||
$aSearchDirs = $this->GivenModuleDiscoveryInit();
|
||||
|
||||
$aInstalledModules = InstallationChoicesToModuleConverter::GetInstance()->GetModules(
|
||||
$this->GivenItilChoices(),
|
||||
$aSearchDirs,
|
||||
__DIR__.'/ressources/installation.xml'
|
||||
);
|
||||
|
||||
$aExpected = [
|
||||
'authent-cas/3.3.0',
|
||||
'authent-external/3.3.0',
|
||||
'authent-ldap/3.3.0',
|
||||
'authent-local/3.3.0',
|
||||
'combodo-backoffice-darkmoon-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-high-contrast-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-protanopia-deuteranopia-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-tritanopia-theme/3.3.0',
|
||||
'itop-attachments/3.3.0',
|
||||
'itop-backup/3.3.0',
|
||||
'itop-config/3.3.0',
|
||||
'itop-files-information/3.3.0',
|
||||
'itop-portal-base/3.3.0',
|
||||
'itop-portal/3.3.0',
|
||||
'itop-profiles-itil/3.3.0',
|
||||
'itop-sla-computation/3.3.0',
|
||||
'itop-structure/3.3.0',
|
||||
'itop-themes-compat/3.3.0',
|
||||
'itop-tickets/3.3.0',
|
||||
'itop-welcome-itil/3.3.0',
|
||||
'combodo-db-tools/3.3.0',
|
||||
'itop-config-mgmt/3.3.0',
|
||||
'itop-core-update/3.3.0',
|
||||
'itop-datacenter-mgmt/3.3.0',
|
||||
'itop-endusers-devices/3.3.0',
|
||||
'itop-hub-connector/3.3.0',
|
||||
'itop-incident-mgmt-itil/3.3.0',
|
||||
'itop-oauth-client/3.3.0',
|
||||
'itop-request-mgmt-itil/3.3.0',
|
||||
'itop-service-mgmt/3.3.0',
|
||||
'itop-storage-mgmt/3.3.0',
|
||||
'itop-virtualization-mgmt/3.3.0',
|
||||
'itop-bridge-cmdb-services/3.3.0',
|
||||
'itop-bridge-cmdb-ticket/3.3.0',
|
||||
'itop-bridge-datacenter-mgmt-services/3.3.0',
|
||||
'itop-bridge-endusers-devices-services/3.3.0',
|
||||
'itop-bridge-storage-mgmt-services/3.3.0',
|
||||
'itop-bridge-virtualization-mgmt-services/3.3.0',
|
||||
'itop-bridge-virtualization-storage/3.3.0',
|
||||
'itop-change-mgmt-itil/3.3.0',
|
||||
'itop-full-itil/3.3.0',
|
||||
];
|
||||
$this->assertEquals($aExpected, $aInstalledModules);
|
||||
}
|
||||
|
||||
//integration test
|
||||
public function testGetModulesWithXmlInstallationFile_LegacyPackages()
|
||||
{
|
||||
$aSearchDirs = $this->GivenModuleDiscoveryInit();
|
||||
|
||||
//no choices means all default ones...
|
||||
$aNoInstallationChoices = [];
|
||||
|
||||
$aInstalledModules = InstallationChoicesToModuleConverter::GetInstance()->GetModules(
|
||||
$aNoInstallationChoices,
|
||||
$aSearchDirs
|
||||
);
|
||||
|
||||
$aExpected = [
|
||||
'authent-cas/3.3.0',
|
||||
'authent-external/3.3.0',
|
||||
'authent-ldap/3.3.0',
|
||||
'authent-local/3.3.0',
|
||||
'combodo-backoffice-darkmoon-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-high-contrast-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-protanopia-deuteranopia-theme/3.3.0',
|
||||
'combodo-backoffice-fullmoon-tritanopia-theme/3.3.0',
|
||||
'itop-backup/3.3.0',
|
||||
'itop-config/3.3.0',
|
||||
'itop-files-information/3.3.0',
|
||||
'itop-portal-base/3.3.0',
|
||||
'itop-profiles-itil/3.3.0',
|
||||
'itop-sla-computation/3.3.0',
|
||||
'itop-structure/3.3.0',
|
||||
'itop-welcome-itil/3.3.0',
|
||||
];
|
||||
$this->assertEquals($aExpected, $aInstalledModules);
|
||||
}
|
||||
|
||||
public function testIsDefaultModule_RootModuleShouldNeverBeDefault()
|
||||
{
|
||||
$sModuleId = ROOT_MODULE;
|
||||
$aModuleInfo = ['category' => 'authentication', 'visible' => false];
|
||||
$this->assertFalse($this->CallIsDefault($sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
public function testIsDefaultModule_AutoselectShouldNeverBeDefault()
|
||||
{
|
||||
$sModuleId = 'autoselect_module';
|
||||
$aModuleInfo = ['category' => 'authentication', 'visible' => false, 'auto_select' => true];
|
||||
$this->assertFalse($this->CallIsDefault($sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
public function testIsDefaultModule_AuthenticationModuleShouldBeDefault()
|
||||
{
|
||||
$sModuleId = 'authentication_module';
|
||||
$aModuleInfo = ['category' => 'authentication', 'visible' => true];
|
||||
$this->assertTrue($this->CallIsDefault($sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
public function testIsDefaultModule_HiddenModuleShouldBeDefault()
|
||||
{
|
||||
$sModuleId = 'hidden_module';
|
||||
$aModuleInfo = ['category' => 'business', 'visible' => false];
|
||||
$this->assertTrue($this->CallIsDefault($sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
public function testIsDefaultModule_NonModuleDefaultCase()
|
||||
{
|
||||
$sModuleId = 'any_module';
|
||||
$aModuleInfo = ['category' => 'business', 'visible' => true];
|
||||
$this->assertFalse($this->CallIsDefault($sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
private function CallIsDefault($sModuleId, $aModuleInfo): bool
|
||||
{
|
||||
return $this->InvokeNonPublicMethod(InstallationChoicesToModuleConverter::class, 'IsDefaultModule', InstallationChoicesToModuleConverter::GetInstance(), [$sModuleId, $aModuleInfo]);
|
||||
}
|
||||
|
||||
public function testIsAutoSelectedModule_RootModuleShouldNeverBeAutoSelect()
|
||||
{
|
||||
$sModuleId = ROOT_MODULE;
|
||||
$aModuleInfo = ['auto_select' => true];
|
||||
$this->assertFalse($this->CallIsAutoSelectedModule([], $sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
public function testIsAutoSelectedModule_NoAutoselectByDefault()
|
||||
{
|
||||
$sModuleId = 'autoselect_module';
|
||||
$aModuleInfo = [];
|
||||
$this->assertFalse($this->CallIsAutoSelectedModule([], $sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* cf DependencyExpression dedicated tests
|
||||
*/
|
||||
public function testIsAutoSelectedModule_UseInstalledModulesForComputation()
|
||||
{
|
||||
$sModuleId = "any_module";
|
||||
$aModuleInfo = ['auto_select' => 'SetupInfo::ModuleIsSelected("a") && SetupInfo::ModuleIsSelected("b")'];
|
||||
$aInstalledModules = ['a' => true, 'b' => true];
|
||||
$this->assertTrue($this->CallIsAutoSelectedModule($aInstalledModules, $sModuleId, $aModuleInfo));
|
||||
}
|
||||
|
||||
private function CallIsAutoSelectedModule($aInstalledModules, $sModuleId, $aModuleInfo): bool
|
||||
{
|
||||
return $this->InvokeNonPublicMethod(InstallationChoicesToModuleConverter::class, 'IsAutoSelectedModule', InstallationChoicesToModuleConverter::GetInstance(), [$aInstalledModules, $sModuleId, $aModuleInfo]);
|
||||
}
|
||||
|
||||
public function testProcessInstallationChoices_Default()
|
||||
{
|
||||
$aRes = [];
|
||||
$aInstallationDescription = $this->GivenInstallationChoiceDescription();
|
||||
|
||||
$this->CallGetModuleNamesFromInstallationChoices([], $aInstallationDescription, $aRes);
|
||||
|
||||
$aExpected = [
|
||||
'combodo-backoffice-darkmoon-theme' => true,
|
||||
'combodo-backoffice-fullmoon-high-contrast-theme' => true,
|
||||
'combodo-backoffice-fullmoon-protanopia-deuteranopia-theme' => true,
|
||||
'combodo-backoffice-fullmoon-tritanopia-theme' => true,
|
||||
'itop-attachments' => true,
|
||||
'itop-backup' => true,
|
||||
'itop-config' => true,
|
||||
'itop-files-information' => true,
|
||||
'itop-profiles-itil' => true,
|
||||
'itop-structure' => true,
|
||||
'itop-themes-compat' => true,
|
||||
'itop-tickets' => true,
|
||||
'itop-welcome-itil' => true,
|
||||
'combodo-db-tools' => true,
|
||||
'itop-config-mgmt' => true,
|
||||
'itop-core-update' => true,
|
||||
'itop-hub-connector' => true,
|
||||
'itop-oauth-client' => true,
|
||||
'combodo-password-expiration' => true,
|
||||
'combodo-webhook-integration' => true,
|
||||
'combodo-my-account-user-info' => true,
|
||||
'authent-token' => true,
|
||||
];
|
||||
$this->assertEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testProcessInstallationChoices_NonItilChoices()
|
||||
{
|
||||
$aRes = [];
|
||||
$aInstallationDescription = $this->GivenInstallationChoiceDescription();
|
||||
|
||||
$this->CallGetModuleNamesFromInstallationChoices($this->GivenNonItilChoices(), $aInstallationDescription, $aRes);
|
||||
|
||||
$aExpected = [
|
||||
'combodo-backoffice-darkmoon-theme' => true,
|
||||
'combodo-backoffice-fullmoon-high-contrast-theme' => true,
|
||||
'combodo-backoffice-fullmoon-protanopia-deuteranopia-theme' => true,
|
||||
'combodo-backoffice-fullmoon-tritanopia-theme' => true,
|
||||
'itop-attachments' => true,
|
||||
'itop-backup' => true,
|
||||
'itop-config' => true,
|
||||
'itop-files-information' => true,
|
||||
'itop-profiles-itil' => true,
|
||||
'itop-structure' => true,
|
||||
'itop-themes-compat' => true,
|
||||
'itop-tickets' => true,
|
||||
'itop-welcome-itil' => true,
|
||||
'combodo-db-tools' => true,
|
||||
'itop-config-mgmt' => true,
|
||||
'itop-core-update' => true,
|
||||
'itop-hub-connector' => true,
|
||||
'itop-oauth-client' => true,
|
||||
'combodo-password-expiration' => true,
|
||||
'combodo-webhook-integration' => true,
|
||||
'combodo-my-account-user-info' => true,
|
||||
'authent-token' => true,
|
||||
'itop-datacenter-mgmt' => true,
|
||||
'itop-endusers-devices' => true,
|
||||
'itop-storage-mgmt' => true,
|
||||
'itop-virtualization-mgmt' => true,
|
||||
'itop-service-mgmt' => true,
|
||||
'itop-request-mgmt' => true,
|
||||
'itop-portal' => true,
|
||||
'itop-portal-base' => true,
|
||||
'itop-change-mgmt' => true,
|
||||
'itop-faq-light' => true,
|
||||
'itop-knownerror-mgmt' => true,
|
||||
];
|
||||
$this->assertEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testProcessInstallationChoices_ItilChoices()
|
||||
{
|
||||
$aRes = [];
|
||||
$aInstallationDescription = $this->GivenInstallationChoiceDescription();
|
||||
|
||||
$this->CallGetModuleNamesFromInstallationChoices($this->GivenItilChoices(), $aInstallationDescription, $aRes);
|
||||
|
||||
$aExpected = [
|
||||
'combodo-backoffice-darkmoon-theme' => true,
|
||||
'combodo-backoffice-fullmoon-high-contrast-theme' => true,
|
||||
'combodo-backoffice-fullmoon-protanopia-deuteranopia-theme' => true,
|
||||
'combodo-backoffice-fullmoon-tritanopia-theme' => true,
|
||||
'itop-attachments' => true,
|
||||
'itop-backup' => true,
|
||||
'itop-config' => true,
|
||||
'itop-files-information' => true,
|
||||
'itop-profiles-itil' => true,
|
||||
'itop-structure' => true,
|
||||
'itop-themes-compat' => true,
|
||||
'itop-tickets' => true,
|
||||
'itop-welcome-itil' => true,
|
||||
'combodo-db-tools' => true,
|
||||
'itop-config-mgmt' => true,
|
||||
'itop-core-update' => true,
|
||||
'itop-hub-connector' => true,
|
||||
'itop-oauth-client' => true,
|
||||
'combodo-password-expiration' => true,
|
||||
'combodo-webhook-integration' => true,
|
||||
'combodo-my-account-user-info' => true,
|
||||
'authent-token' => true,
|
||||
'itop-datacenter-mgmt' => true,
|
||||
'itop-endusers-devices' => true,
|
||||
'itop-storage-mgmt' => true,
|
||||
'itop-virtualization-mgmt' => true,
|
||||
'itop-service-mgmt' => true,
|
||||
'itop-portal' => true,
|
||||
'itop-portal-base' => true,
|
||||
'itop-request-mgmt-itil' => true,
|
||||
'itop-incident-mgmt-itil' => true,
|
||||
'itop-change-mgmt-itil' => true,
|
||||
];
|
||||
$this->assertEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
private function CallGetModuleNamesFromInstallationChoices(array $aInstallationChoices, array $aInstallationDescription, array &$aModuleNames)
|
||||
{
|
||||
$this->InvokeNonPublicMethod(
|
||||
InstallationChoicesToModuleConverter::class,
|
||||
'GetModuleNamesFromInstallationChoices',
|
||||
InstallationChoicesToModuleConverter::GetInstance(),
|
||||
[$aInstallationChoices, $aInstallationDescription, &$aModuleNames]
|
||||
);
|
||||
}
|
||||
|
||||
private function GivenInstallationChoiceDescription(): array
|
||||
{
|
||||
$oXMLParameters = new XMLParameters(__DIR__."/ressources/installation.xml");
|
||||
return $oXMLParameters->Get('steps', []);
|
||||
}
|
||||
|
||||
private function GivenAllModules(): array
|
||||
{
|
||||
return json_decode(file_get_contents(__DIR__.'/ressources/available_modules.json'), true);
|
||||
}
|
||||
|
||||
private function GivenNonItilChoices(): array
|
||||
{
|
||||
return [
|
||||
'itop-config-mgmt-core',
|
||||
'itop-config-mgmt-datacenter',
|
||||
'itop-config-mgmt-end-user',
|
||||
'itop-config-mgmt-storage',
|
||||
'itop-config-mgmt-virtualization',
|
||||
'itop-service-mgmt-enterprise',
|
||||
'itop-ticket-mgmt-simple-ticket',
|
||||
'itop-ticket-mgmt-simple-ticket-enhanced-portal',
|
||||
'itop-change-mgmt-simple',
|
||||
'itop-kown-error-mgmt',
|
||||
];
|
||||
}
|
||||
|
||||
private function GivenItilChoices(): array
|
||||
{
|
||||
return [
|
||||
'itop-config-mgmt-datacenter',
|
||||
'itop-config-mgmt-end-user',
|
||||
'itop-config-mgmt-storage',
|
||||
'itop-config-mgmt-virtualization',
|
||||
'itop-service-mgmt-enterprise',
|
||||
'itop-ticket-mgmt-itil',
|
||||
'itop-ticket-mgmt-itil-user-request',
|
||||
'itop-ticket-mgmt-itil-incident',
|
||||
'itop-ticket-mgmt-itil-enhanced-portal',
|
||||
'itop-change-mgmt-itil',
|
||||
'itop-config-mgmt-core',
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
private function GivenModuleDiscoveryInit(): array
|
||||
{
|
||||
$aSearchDirs = [APPROOT.'datamodels/2.x'];
|
||||
$this->SetNonPublicStaticProperty(ModuleDiscovery::class, 'm_aSearchDirs', $aSearchDirs);
|
||||
$aAllModules = $this->GivenAllModules();
|
||||
$this->SetNonPublicStaticProperty(ModuleDiscovery::class, 'm_aModules', $aAllModules);
|
||||
return $aSearchDirs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installation>
|
||||
<steps type="array">
|
||||
<step>
|
||||
<title>Configuration Management options</title>
|
||||
<description><![CDATA[<h2>The options below allow you to configure the type of elements that are to be managed inside iTop.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-apps-tab.svg</banner>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-core</extension_code>
|
||||
<title>Configuration Management Core</title>
|
||||
<description>All the base objects that are mandatory in the iTop CMDB: Organizations, Locations, Teams, Persons, etc.</description>
|
||||
<modules type="array">
|
||||
<module>combodo-backoffice-darkmoon-theme</module>
|
||||
<module>combodo-backoffice-fullmoon-high-contrast-theme</module>
|
||||
<module>combodo-backoffice-fullmoon-protanopia-deuteranopia-theme</module>
|
||||
<module>combodo-backoffice-fullmoon-tritanopia-theme</module>
|
||||
<module>combodo-db-tools</module>
|
||||
<module>combodo-password-expiration</module>
|
||||
<module>combodo-webhook-integration</module>
|
||||
<module>itop-attachments</module>
|
||||
<module>itop-backup</module>
|
||||
<module>itop-config</module>
|
||||
<module>itop-config-mgmt</module>
|
||||
<module>itop-core-update</module>
|
||||
<module>itop-files-information</module>
|
||||
<module>itop-hub-connector</module>
|
||||
<module>itop-oauth-client</module>
|
||||
<module>itop-profiles-itil</module>
|
||||
<module>itop-structure</module>
|
||||
<module>itop-themes-compat</module>
|
||||
<module>itop-tickets</module>
|
||||
<module>itop-welcome-itil</module>
|
||||
<module>combodo-my-account-user-info</module>
|
||||
<module>authent-token</module>
|
||||
</modules>
|
||||
<mandatory>true</mandatory>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-datacenter</extension_code>
|
||||
<title>Data Center Devices</title>
|
||||
<description>Manage Data Center devices such as Racks, Enclosures, PDUs, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-datacenter-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-end-user</extension_code>
|
||||
<title>End-User Devices</title>
|
||||
<description>Manage devices related to end-users: PCs, Phones, Tablets, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-endusers-devices</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-storage</extension_code>
|
||||
<title>Storage Devices</title>
|
||||
<description>Manage storage devices such as NAS, SAN Switches, Tape Libraries and Tapes, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-storage-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-virtualization</extension_code>
|
||||
<title>Virtualization</title>
|
||||
<description>Manage Hypervisors, Virtual Machines and Farms.</description>
|
||||
<modules type="array">
|
||||
<module>itop-virtualization-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</step>
|
||||
<step>
|
||||
<title>Service Management options</title>
|
||||
<description><![CDATA[<h2>Select the choice that best describes the relationships between the services and the IT infrastructure in your IT environment.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-services.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-service-mgmt-enterprise</extension_code>
|
||||
<title>Service Management for Enterprises</title>
|
||||
<description>Select this option if the IT delivers services based on a shared infrastructure. For example if different organizations within your company subscribe to services (like Mail and Print services) delivered by a single shared backend.</description>
|
||||
<modules type="array">
|
||||
<module>itop-service-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-service-mgmt-service-provider</extension_code>
|
||||
<title>Service Management for Service Providers</title>
|
||||
<description>Select this option if the IT manages the infrastructure of independent customers. This is the most flexible model, since the services can be delivered with a mix of shared and customer specific infrastructure devices.</description>
|
||||
<modules type="array">
|
||||
<module>itop-service-mgmt-provider</module>
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Tickets Management options</title>
|
||||
<description><![CDATA[<h2>Select the type of tickets you want to use in order to respond to user requests and incidents.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-discussion-forum.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-simple-ticket</extension_code>
|
||||
<title>Simple Ticket Management</title>
|
||||
<description>Select this option to use one single type of tickets for all kind of requests.</description>
|
||||
<modules type="array">
|
||||
<module>itop-request-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
<sub_options>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-simple-ticket-enhanced-portal</extension_code>
|
||||
<title>Customer Portal</title>
|
||||
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
|
||||
<modules type="array">
|
||||
<module>itop-portal</module>
|
||||
<module>itop-portal-base</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil</extension_code>
|
||||
<title>ITIL Compliant Tickets Management</title>
|
||||
<description>Select this option to have different types of ticket for managing user requests and incidents. Each type of ticket has a specific life cycle and specific fields</description>
|
||||
<sub_options>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-user-request</extension_code>
|
||||
<title>User Request Management</title>
|
||||
<description>Manage User Request tickets in iTop</description>
|
||||
<modules type="array">
|
||||
<module>itop-request-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-incident</extension_code>
|
||||
<title>Incident Management</title>
|
||||
<description>Manage Incidents tickets in iTop</description>
|
||||
<modules type="array">
|
||||
<module>itop-incident-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-enhanced-portal</extension_code>
|
||||
<title>Customer Portal</title>
|
||||
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
|
||||
<modules type="array">
|
||||
<module>itop-portal</module>
|
||||
<module>itop-portal-base</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-none</extension_code>
|
||||
<title>No Tickets Management</title>
|
||||
<description>Don't manage incidents or user requests in iTop</description>
|
||||
<modules type="array">
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Change Management options</title>
|
||||
<description><![CDATA[<h2>Select the type of tickets you want to use in order to manage changes to the IT infrastructure.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-change.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-simple</extension_code>
|
||||
<title>Simple Change Management</title>
|
||||
<description>Select this option to use one type of ticket for all kind of changes.</description>
|
||||
<modules type="array">
|
||||
<module>itop-change-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-itil</extension_code>
|
||||
<title>ITIL Change Management</title>
|
||||
<description>Select this option to use Normal/Routine/Emergency change tickets.</description>
|
||||
<modules type="array">
|
||||
<module>itop-change-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-none</extension_code>
|
||||
<title>No Change Management</title>
|
||||
<description>Don't manage changes in iTop</description>
|
||||
<modules type="array">
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Additional ITIL tickets</title>
|
||||
<description><![CDATA[<h2>Pick from the list below the additional ITIL processes that are to be implemented in iTop.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-important-book.svg</banner>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<!-- Extension code has a typo but fixing it would remove that setup option for all existing iTop -->
|
||||
<extension_code>itop-kown-error-mgmt</extension_code>
|
||||
<title>Known Errors Management and FAQ</title>
|
||||
<description>Select this option to track "Known Errors" and FAQs in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-faq-light</module>
|
||||
<module>itop-knownerror-mgmt</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-problem-mgmt</extension_code>
|
||||
<title>Problem Management</title>
|
||||
<description>Select this option track "Problems" in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-problem-mgmt</module>
|
||||
</modules>
|
||||
</choice>
|
||||
</options>
|
||||
</step>
|
||||
</steps>
|
||||
</installation>
|
||||
@@ -0,0 +1 @@
|
||||
{"itop-config-mgmt":{"label":"Configuration+Management+customized+for+Combodo+IT(CMDB)","value":"2.7.0"},"itop-icalendar-action":{"label":"Calendar+Invitations","value":"1.1.0"},"itop-fence":{"label":"iTop+Fence","value":"1.1.2"},"authent-ldap":{"label":"User+authentication+based+on+LDAP","value":"3.2.1"},"itop-faq-light":{"label":"Frequently+Asked+Questions+Database","value":"3.2.1"},"authent-local":{"label":"User+authentication+based+on+the+local+DB","value":"3.2.1"},"combodo-custom-hyperlinks":{"label":"Hyperlinks+configurator","value":"1.1.3"},"authent-token":{"label":"User+authentication+by+token","value":"2.2.1"},"itop-service-mgmt":{"label":"Service+Management+Customized+for+Combodo+IT(services,+SLAs,+contracts)","value":"2.7.0"},"combodo-impersonate":{"label":"Impersonate+user+for+support","value":"1.2.1"},"combodo-hybridauth":{"label":"oAuth\/OpenID+authentication","value":"1.2.4"},"combodo-login-page":{"label":"Combodo+login+page","value":"2.1.0"},"itop-core-update":{"label":"iTop+Core+Update","value":"3.2.1"},"itop-communications":{"label":"Communications+to+the+Customers","value":"1.3.4"},"itsm-designer-connector":{"label":"ITSM+Designer+Connector","value":"1.8.3"},"authent-external":{"label":"External+user+authentication","value":"3.2.1"},"itop-object-copier":{"label":"Object+copier","value":"1.4.5"},"combodo-backoffice-compact-themes":{"label":"Backoffice:+compact+themes","value":"1.0.1"},"data-localizer":{"label":"Data+localizer","value":"1.3.4"},"combodo-support-portal":{"label":"Combodo+Support+Portal","value":"3.0.1"},"combodo-calendar-view":{"label":"Calendar+View","value":"2.2.1"},"combodo-email-synchro":{"label":"Tickets+synchronization+via+e-mail","value":"3.8.2"},"combodo-webhook-integration":{"label":"Webhook+integrations","value":"1.4.1"},"combodo-notify-on-expiration":{"label":"Notify+on+expiration","value":"1.0.4"},"combodo-db-tools":{"label":"Database+maintenance+tools","value":"3.2.1"},"precanned-replies":{"label":"Helpdesk+Precanned+Replies","value":"1.4.0"},"combodo-dokuwiki-portal-brick":{"label":"Docuwiki+brick+(Portal)","value":"1.2.0"},"itop-rh-mgmt":{"label":"Human+Resource+Management","value":"2.7.0"},"itop-request-mgmt":{"label":"User+request+management+(Service+Desk)","value":"2.7.0"},"customer-survey":{"label":"Customer+Survey","value":"2.5.5"},"itop-standard-email-synchro":{"label":"Ticket+Creation+from+Emails+(Standard)","value":"3.8.2"},"itop-system-information":{"label":"System+information","value":"1.2.6"},"itop-sales-mgmt":{"label":"Sales+Management","value":"2.7.0"},"combodo-password-expiration":{"label":"Password+Expiration+Enforcement","value":"1.0.0"},"combodo-workflow-graphical-view":{"label":"Workflow+graphical+view","value":"1.1.3"},"combodo-itsm-master":{"label":"Data+master+for+the+ITSM+Designer","value":"2.7.0"},"combodo-email-tickets":{"label":"Tickets+Creation+from+Emails+for+Combodo","value":"2.7.0"},"itop-training-mgmt":{"label":"Training+Management","value":"2.7.0"},"precanned-replies-pro":{"label":"Helpdesk+Precanned+Replies+Extension","value":"1.2.0"},"combodo-fulltext-search":{"label":"Enhanced+global+search","value":"2.0.0"},"itop-request-template":{"label":"Customized+Request+Forms","value":"2.3.6"},"itop-rest-data-push":{"label":"Data+push+(based+on+standard+REST+services)","value":"1.0.2"},"combodo-kpi-logger":{"label":"KPI+logger","value":"1.0.3"},"itop-incident-mgmt":{"label":"Incident+Management","value":"2.7.0"},"combodo-my-account-user-info":{"label":"User+info+for+MyAccount+module","value":"1.0.0"},"email-reply":{"label":"Send+Ticket+Log+Updates+by+Email","value":"1.4.5"},"itop-attachments":{"label":"Tickets+Attachments","value":"3.2.1"},"itop-log-mgmt":{"label":"iTop+Log+management","value":"2.0.8"},"itop-ui-copypaste":{"label":"CopyPaste+UI+Component","value":"1.0.0"}}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installation>
|
||||
<steps type="array">
|
||||
<step>
|
||||
<title>Configuration Management options</title>
|
||||
<description><![CDATA[<h2>The options below allow you to configure the type of elements that are to be managed inside iTop.</h2>]]></description>
|
||||
<banner>/images/modules.png</banner>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-core</extension_code>
|
||||
<title>Configuration Management Core</title>
|
||||
<description>All the base objects that are mandatory in the iTop CMDB: Organizations, Locations, Teams, Persons, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-config-mgmt</module>
|
||||
<module>itop-attachments</module>
|
||||
<module>itop-profiles-itil</module>
|
||||
<module>itop-welcome-itil</module>
|
||||
<module>itop-tickets</module>
|
||||
<module>itop-files-information</module>
|
||||
<module>combodo-db-tools</module>
|
||||
<module>itop-core-update</module>
|
||||
<module>itop-hub-connector</module>
|
||||
<module>itop-oauth-client</module>
|
||||
</modules>
|
||||
<mandatory>true</mandatory>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-datacenter</extension_code>
|
||||
<title>Data Center Devices</title>
|
||||
<description>Manage Data Center devices such as Racks, Enclosures, PDUs, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-datacenter-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-end-user</extension_code>
|
||||
<title>End-User Devices</title>
|
||||
<description>Manage devices related to end-users: PCs, Phones, Tablets, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-endusers-devices</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-storage</extension_code>
|
||||
<title>Storage Devices</title>
|
||||
<description>Manage storage devices such as NAS, SAN Switches, Tape Libraries and Tapes, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-storage-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-virtualization</extension_code>
|
||||
<title>Virtualization</title>
|
||||
<description>Manage Hypervisors, Virtual Machines and Farms.</description>
|
||||
<modules type="array">
|
||||
<module>itop-virtualization-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</step>
|
||||
<step>
|
||||
<title>Service Management options</title>
|
||||
<description><![CDATA[<h2>Select the choice that best describes the relationships between the services and the IT infrastructure in your IT environment.</h2>]]></description>
|
||||
<banner>./wizard-icons/service.png</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-service-mgmt-enterprise</extension_code>
|
||||
<title>Service Management for Enterprises</title>
|
||||
<description>Select this option if the IT delivers services based on a shared infrastructure. For example if different organizations within your company subscribe to services (like Mail and Print services) delivered by a single shared backend.</description>
|
||||
<modules type="array">
|
||||
<module>itop-service-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-service-mgmt-service-provider</extension_code>
|
||||
<title>Service Management for Service Providers</title>
|
||||
<description>Select this option if the IT manages the infrastructure of independent customers. This is the most flexible model, since the services can be delivered with a mix of shared and customer specific infrastructure devices.</description>
|
||||
<modules type="array">
|
||||
<module>itop-service-mgmt-provider</module>
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Tickets Management options</title>
|
||||
<description><![CDATA[<h2>Select the type of tickets you want to use in order to respond to user requests and incidents.</h2>]]></description>
|
||||
<banner>./itop-incident-mgmt-itil/images/incident-escalated.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-simple-ticket</extension_code>
|
||||
<title>Simple Ticket Management</title>
|
||||
<description>Select this option to use one single type of tickets for all kind of requests.</description>
|
||||
<modules type="array">
|
||||
<module>itop-request-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
<sub_options>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-simple-ticket-enhanced-portal</extension_code>
|
||||
<title>Customer Portal</title>
|
||||
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
|
||||
<modules type="array">
|
||||
<module>itop-portal</module>
|
||||
<module>itop-portal-base</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil</extension_code>
|
||||
<title>ITIL Compliant Tickets Management</title>
|
||||
<description>Select this option to have different types of ticket for managing user requests and incidents. Each type of ticket has a specific life cycle and specific fields</description>
|
||||
<sub_options>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-user-request</extension_code>
|
||||
<title>User Request Management</title>
|
||||
<description>Manage User Request tickets in iTop</description>
|
||||
<modules type="array">
|
||||
<module>itop-request-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-incident</extension_code>
|
||||
<title>Incident Management</title>
|
||||
<description>Manage Incidents tickets in iTop</description>
|
||||
<modules type="array">
|
||||
<module>itop-incident-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-enhanced-portal</extension_code>
|
||||
<title>Customer Portal</title>
|
||||
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
|
||||
<modules type="array">
|
||||
<module>itop-portal</module>
|
||||
<module>itop-portal-base</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-none</extension_code>
|
||||
<title>No Tickets Management</title>
|
||||
<description>Don't manage incidents or user requests in iTop</description>
|
||||
<modules type="array">
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Change Management options</title>
|
||||
<description><![CDATA[<h2>Select the type of tickets you want to use in order to manage changes to the IT infrastructure.</h2>]]></description>
|
||||
<banner>./itop-change-mgmt/images/change.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-simple</extension_code>
|
||||
<title>Simple Change Management</title>
|
||||
<description>Select this option to use one type of ticket for all kind of changes.</description>
|
||||
<modules type="array">
|
||||
<module>itop-change-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-itil</extension_code>
|
||||
<title>ITIL Change Management</title>
|
||||
<description>Select this option to use Normal/Routine/Emergency change tickets.</description>
|
||||
<modules type="array">
|
||||
<module>itop-change-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-none</extension_code>
|
||||
<title>No Change Management</title>
|
||||
<description>Don't manage changes in iTop</description>
|
||||
<modules type="array">
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Additional ITIL tickets</title>
|
||||
<description><![CDATA[<h2>Pick from the list below the additional ITIL processes that are to be implemented in iTop.</h2>]]></description>
|
||||
<banner>./itop-knownerror-mgmt/images/known-error.svg</banner>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-kown-error-mgmt</extension_code>
|
||||
<title>Known Errors Management</title>
|
||||
<description>Select this option to track "Known Errors" and FAQs in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-knownerror-mgmt</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-problem-mgmt</extension_code>
|
||||
<title>Problem Management</title>
|
||||
<description>Select this option track "Problems" in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-problem-mgmt</module>
|
||||
</modules>
|
||||
</choice>
|
||||
</options>
|
||||
</step>
|
||||
</steps>
|
||||
</installation>
|
||||
Reference in New Issue
Block a user