mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-14 16:04:10 +01:00
Compare commits
21 Commits
develop
...
feature/88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aac64cc19 | ||
|
|
f668d94bd9 | ||
|
|
00e071228e | ||
|
|
19b7f851f7 | ||
|
|
f9a1b444ab | ||
|
|
3b38dac8c6 | ||
|
|
b967fb7f20 | ||
|
|
195038c941 | ||
|
|
9a8d87e2b5 | ||
|
|
385302c44c | ||
|
|
b563f113d0 | ||
|
|
715d9d3b1c | ||
|
|
7a6c2bc6a4 | ||
|
|
8919184ef9 | ||
|
|
7bfaebe4db | ||
|
|
8e10cf9b72 | ||
|
|
9f3d7d2c36 | ||
|
|
ae980e365d | ||
|
|
a2b01b3ed4 | ||
|
|
9cdc707bc5 | ||
|
|
76178c16b8 |
@@ -462,6 +462,19 @@ abstract class MetaModel
|
||||
return call_user_func([$sClass, 'GetClassDescription'], $sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetCreatedIn($sClass)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
|
||||
return self::$m_aClassParams[$sClass]["created_in"] ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
@@ -3145,6 +3158,7 @@ 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",
|
||||
|
||||
@@ -135,7 +135,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment
|
||||
$aAvailableModules[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
// TODO check the auto-selected modules here
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if ($oExtension->bMarkedAsChosen) {
|
||||
foreach ($oExtension->aModules as $sModuleName) {
|
||||
if (!isset($aRet[$sModuleName]) && isset($aAvailableModules[$sModuleName])) {
|
||||
|
||||
@@ -186,9 +186,7 @@ function collect_configuration()
|
||||
|
||||
// iTop modules
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
// Get the latest installed modules, without the "root" ones (iTop version and datamodel version)
|
||||
$aInstalledModules = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install WHERE installed = '".$sLatestInstallationDate."' AND parent_id != 0");
|
||||
$aInstalledModules = ModuleInstallationService::GetInstance()->ReadFromDB($oConfig);
|
||||
|
||||
foreach ($aInstalledModules as $aDBInfo) {
|
||||
$aConfiguration['itop_modules'][$aDBInfo['name']] = $aDBInfo['version'];
|
||||
|
||||
@@ -26,23 +26,12 @@ use Composer\Semver\VersionParser;
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
@@ -320,24 +309,6 @@ class InstalledVersions
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,9 +325,7 @@ class InstalledVersions
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
@@ -364,14 +333,11 @@ class InstalledVersions
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'name' => 'combodo/itop',
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
|
||||
'reference' => '469afdb2f9aea1b6e078a2a5bb12f09a969d60e0',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -22,7 +22,7 @@
|
||||
'combodo/itop' => array(
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
|
||||
'reference' => '469afdb2f9aea1b6e078a2a5bb12f09a969d60e0',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
||||
130
setup/AnalyzeInstallation.php
Normal file
130
setup/AnalyzeInstallation.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/ModuleInstallationService.php';
|
||||
|
||||
class AnalyzeInstallation
|
||||
{
|
||||
private static AnalyzeInstallation $oInstance;
|
||||
private ?array $aAvailableModules = null;
|
||||
private ?array $aSelectInstall = null;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): AnalyzeInstallation
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new AnalyzeInstallation();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?AnalyzeInstallation $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the current installation and the possibilities
|
||||
*
|
||||
* @param null|Config $oConfig Defines the target environment (DB)
|
||||
* @param mixed $modulesPath Either a single string or an array of absolute paths
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array Array with the following format:
|
||||
* array =>
|
||||
* 'iTop' => array(
|
||||
* 'installed_version' => ... (could be empty in case of a fresh install)
|
||||
* 'available_version => ...
|
||||
* )
|
||||
* <module_name> => array(
|
||||
* 'installed_version' => ...
|
||||
* 'available_version' => ...
|
||||
* 'install' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
* )
|
||||
* 'uninstall' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
* )
|
||||
* 'label' => ...
|
||||
* 'dependencies' => array(<module1>, <module2>, ...)
|
||||
* 'visible' => true | false
|
||||
* )
|
||||
* )
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function AnalyzeInstallation(?Config $oConfig, mixed $modulesPath, bool $bAbortOnMissingDependency = false, ?array $aModulesToLoad = null)
|
||||
{
|
||||
$aRes = [
|
||||
ROOT_MODULE => [
|
||||
'installed_version' => '',
|
||||
'available_version' => ITOP_VERSION_FULL,
|
||||
'name_code' => ITOP_APPLICATION,
|
||||
],
|
||||
];
|
||||
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
|
||||
if (! is_null($this->aAvailableModules)) {
|
||||
//test only
|
||||
$aAvailableModules = $this->aAvailableModules;
|
||||
} else {
|
||||
$aAvailableModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
foreach ($aAvailableModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
$aModuleInfo['installed_version'] = '';
|
||||
$aModuleInfo['available_version'] = $sModuleVersion;
|
||||
|
||||
if ($aModuleInfo['mandatory']) {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_MANDATORY,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
$aRes[$sModuleName] = $aModuleInfo;
|
||||
}
|
||||
|
||||
$aCurrentlyInstalledModules = ModuleInstallationService::GetInstance()->ReadComputeInstalledModules($oConfig);
|
||||
|
||||
// Adjust the list of proposed modules
|
||||
foreach ($aCurrentlyInstalledModules as $sModuleName => $aModuleDB) {
|
||||
if ($sModuleName == ROOT_MODULE) {
|
||||
$aRes[$sModuleName]['installed_version'] = $aModuleDB['version'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($sModuleName, $aRes)) {
|
||||
// A module was installed, it is not proposed in the new build... skip
|
||||
continue;
|
||||
}
|
||||
|
||||
$aRes[$sModuleName]['installed_version'] = $aModuleDB['version'];
|
||||
|
||||
if ($aRes[$sModuleName]['mandatory']) {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
}
|
||||
184
setup/ModuleInstallationService.php
Normal file
184
setup/ModuleInstallationService.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
class ModuleInstallationService
|
||||
{
|
||||
private static ModuleInstallationService $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModuleInstallationService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModuleInstallationService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModuleInstallationService $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
private ?array $aSelectInstall = null;
|
||||
|
||||
/**
|
||||
* @param \Config|null $oConfig
|
||||
* @return array
|
||||
*/
|
||||
public function ReadComputeInstalledModules(?Config $oConfig): array
|
||||
{
|
||||
$aSelectInstall = [];
|
||||
try {
|
||||
$aSelectInstall = $this->ReadFromDB($oConfig);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
}
|
||||
|
||||
return $this->ComputeInstalledModules($aSelectInstall);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config|null $oConfig
|
||||
* @return array
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
*/
|
||||
public function ReadFromDB(?Config $oConfig): array
|
||||
{
|
||||
if (is_null($oConfig)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! is_null($this->aSelectInstall)) {
|
||||
//test only
|
||||
return $this->aSelectInstall;
|
||||
}
|
||||
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
//read db module installations
|
||||
$tableWithPrefix = $this->GetTableWithPrefix($oConfig);
|
||||
$iRootId = CMDBSource::QueryToScalar("SELECT max(parent_id) FROM $tableWithPrefix");
|
||||
// Get the latest installed modules, without the "root" ones (iTop version and datamodel version)
|
||||
$sSQL = <<<SQL
|
||||
SELECT * FROM $tableWithPrefix
|
||||
WHERE
|
||||
parent_id='$iRootId'
|
||||
OR id='$iRootId'
|
||||
SQL;
|
||||
return CMDBSource::QueryToArray($sSQL);
|
||||
}
|
||||
|
||||
private function GetTableWithPrefix(Config $oConfig)
|
||||
{
|
||||
$sPrefix = $oConfig->Get('db_subname');
|
||||
if (utils::IsNullOrEmptyString($sPrefix)) {
|
||||
return "priv_module_install";
|
||||
}
|
||||
|
||||
return "{$sPrefix}priv_module_install";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config $oConfig
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function GetApplicationVersion(Config $oConfig)
|
||||
{
|
||||
try {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$tableWithPrefix = $this->GetTableWithPrefix($oConfig);
|
||||
$sSQLQuery = "SELECT * FROM $tableWithPrefix";
|
||||
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
$this->log_error('Can not connect to the database: host: '.$oConfig->Get('db_host').', user:'.$oConfig->Get('db_user').', pwd:'.$oConfig->Get('db_pwd').', db name:'.$oConfig->Get('db_name'));
|
||||
$this->log_error('Exception '.$e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$aResult = [];
|
||||
// Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
if ($aInstall['name'] == DATAMODEL_MODULE) {
|
||||
$aResult['datamodel_version'] = $sModuleVersion;
|
||||
$aComments = json_decode($aInstall['comment'], true);
|
||||
if (is_array($aComments)) {
|
||||
$aResult = array_merge($aResult, $aComments);
|
||||
}
|
||||
} else {
|
||||
$aResult['product_name'] = $aInstall['name'];
|
||||
$aResult['product_version'] = $sModuleVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('datamodel_version', $aResult)) {
|
||||
// Versions prior to 2.0 did not record the version of the datamodel
|
||||
// so assume that the datamodel version is equal to the application version
|
||||
$aResult['datamodel_version'] = $aResult['product_version'];
|
||||
}
|
||||
$this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']);
|
||||
return empty($aResult) ? false : $aResult;
|
||||
}
|
||||
|
||||
private function ComputeInstalledModules(array $aSelectInstall): array
|
||||
{
|
||||
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
|
||||
//module installation datetime is mostly the same for all modules
|
||||
//unless there was issue recording things in DB
|
||||
$sFirstDatetime = null;
|
||||
$iFirstTime = -1;
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
//$aInstall['comment']; // unsused
|
||||
$sDatetime = $aInstall['installed'];
|
||||
|
||||
if (is_null($sFirstDatetime)) {
|
||||
$sFirstDatetime = $sDatetime;
|
||||
$iFirstTime = strtotime($sDatetime);
|
||||
$iInstalled = $iFirstTime;
|
||||
} elseif ($sDatetime === $sFirstDatetime) {
|
||||
$iInstalled = $iFirstTime;
|
||||
} else {
|
||||
$sDatetime = $aInstall['installed'];
|
||||
$iInstalled = strtotime($sDatetime);
|
||||
}
|
||||
|
||||
$sModuleName = $aInstall['name'];
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$aInstallByModule[ROOT_MODULE] = [
|
||||
'installed_version' => $sModuleVersion,
|
||||
'installed' => $iInstalled,
|
||||
'version' => $sModuleVersion,
|
||||
];
|
||||
} else {
|
||||
$aInstallByModule[$sModuleName] = [
|
||||
'installed' => $iInstalled,
|
||||
'version' => $sModuleVersion,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aInstallByModule;
|
||||
}
|
||||
}
|
||||
@@ -477,7 +477,7 @@ class MFCompiler
|
||||
$sClass = $oClass->getAttribute("id");
|
||||
$aAllClasses[] = $sClass;
|
||||
try {
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
} catch (DOMFormatException $e) {
|
||||
$sMessage = "Failed to process class '$sClass', ";
|
||||
if (!empty($sModuleRootDir)) {
|
||||
@@ -1189,6 +1189,7 @@ EOF
|
||||
|
||||
/**
|
||||
* @param \MFElement $oClass
|
||||
* @param string $sModuleName
|
||||
* @param string $sTempTargetDir
|
||||
* @param string $sFinalTargetDir
|
||||
* @param string $sModuleRelativeDir
|
||||
@@ -1196,7 +1197,7 @@ EOF
|
||||
* @return string
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
protected function CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
protected function CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
{
|
||||
$sClass = $oClass->getAttribute('id');
|
||||
$oProperties = $oClass->GetUniqueElement('properties');
|
||||
@@ -1209,6 +1210,7 @@ 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';
|
||||
}
|
||||
|
||||
@@ -148,32 +148,38 @@ class iTopExtensionsMap
|
||||
{
|
||||
/**
|
||||
* The list of all discovered extensions
|
||||
* @param string $sFromEnvironment The environment to scan
|
||||
* @param bool $bNormailizeOldExtension true to "magically" convert some well-known old extensions (i.e. a set of modules) to the new iTopExtension format
|
||||
* @return void
|
||||
* @var array $aExtensions
|
||||
*/
|
||||
protected $aExtensions;
|
||||
/**
|
||||
* The list of all currently installed extensions
|
||||
* @var array|null
|
||||
* @var array $aInstalledExtensions
|
||||
*/
|
||||
protected ?array $aInstalledExtensions = null;
|
||||
protected array $aInstalledExtensions;
|
||||
|
||||
protected array $aExtensionsByCode;
|
||||
/**
|
||||
* The list of directories browsed using the ReadDir method when building the map
|
||||
* @var string[]
|
||||
*/
|
||||
protected $aScannedDirs;
|
||||
|
||||
/**
|
||||
* The list of all discovered extensions
|
||||
* @param string $sFromEnvironment The environment to scan
|
||||
* @param bool $bNormailizeOldExtension true to "magically" convert some well-known old extensions (i.e. a set of modules) to the new iTopExtension format
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
|
||||
{
|
||||
$this->aExtensions = [];
|
||||
$this->aExtensionsByCode = [];
|
||||
$this->aScannedDirs = [];
|
||||
$this->ScanDisk($sFromEnvironment);
|
||||
foreach ($aExtraDirs as $sDir) {
|
||||
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
$this->CheckDependencies($sFromEnvironment);
|
||||
$this->CheckDependencies();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,8 +190,10 @@ class iTopExtensionsMap
|
||||
*/
|
||||
protected function ScanDisk($sEnvironment)
|
||||
{
|
||||
if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x') && !$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x')) {
|
||||
if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x')) {
|
||||
//no installation xml found in 2.x: let's read all extensions in 2.x first
|
||||
if (!$this->ReadDir(APPROOT.'/datamodels/2.x', iTopExtension::SOURCE_WIZARD)) {
|
||||
//nothing found in 2.x : fallback read in 1.x (flat structure)
|
||||
$this->ReadDir(APPROOT.'/datamodels/1.x', iTopExtension::SOURCE_WIZARD);
|
||||
}
|
||||
}
|
||||
@@ -261,6 +269,7 @@ class iTopExtensionsMap
|
||||
// This "new" extension is "newer" than the previous one, let's replace the previous one
|
||||
unset($this->aExtensions[$key]);
|
||||
$this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
|
||||
$this->aExtensionsByCode[$oNewExtension->sCode] = $oNewExtension;
|
||||
return;
|
||||
} else {
|
||||
// This "new" extension is not "newer" than the previous one, let's ignore it
|
||||
@@ -270,6 +279,22 @@ class iTopExtensionsMap
|
||||
}
|
||||
// Finally it's not a duplicate, let's add it to the list
|
||||
$this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
|
||||
$this->aExtensionsByCode[$oNewExtension->sCode] = $oNewExtension;
|
||||
}
|
||||
|
||||
public function RemoveExtension(string $sCode): void
|
||||
{
|
||||
$oExtension = $this->GetFromExtensionCode($sCode);
|
||||
if (is_null($oExtension)) {
|
||||
\IssueLog::Error(__METHOD__.": cannot find extension to remove", null, [$sCode]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
\IssueLog::Debug(__METHOD__.": remove extension from map", null, [$oExtension->sCode => $oExtension->sSourceDir]);
|
||||
|
||||
unset($this->aExtensions[$oExtension->sCode.'/'.$oExtension->sVersion]);
|
||||
unset($this->aExtensionsByCode[$sCode]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,13 +305,31 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function GetFromExtensionCode(string $sExtensionCode): ?iTopExtension
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode === $sExtensionCode) {
|
||||
return $oExtension;
|
||||
return $this->aExtensionsByCode[$sExtensionCode] ?? null;
|
||||
}
|
||||
|
||||
/*public function GetMissingExtensions(array $aSelectedExtensions)
|
||||
{
|
||||
\SetupLog::Info(__METHOD__, null, ['selected' => $aSelectedExtensions]);
|
||||
$aExtensionsFromDb = array_keys($this->aExtensionsByCode);
|
||||
sort($aExtensionsFromDb);
|
||||
\SetupLog::Info(__METHOD__, null, ['found' => $aExtensionsFromDb]);
|
||||
|
||||
$aRes = [];
|
||||
foreach (array_diff($aExtensionsFromDb, $aSelectedExtensions) as $sExtensionCode) {
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension) && $oExtension->bVisible && $oExtension->sSource != iTopExtension::SOURCE_WIZARD) {
|
||||
|
||||
\SetupLog::Info(__METHOD__."$sExtensionCode", null, ['visible' => $oExtension->bVisible, 'mandatory' => $oExtension->bMandatory]);
|
||||
$aRes [] = $sExtensionCode;
|
||||
} else {
|
||||
\SetupLog::Info(__METHOD__." MISSING $sExtensionCode");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
\SetupLog::Info(__METHOD__, null, $aRes);
|
||||
|
||||
return $aRes;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Read (recursively) a directory to find if it contains extensions (or modules)
|
||||
@@ -346,19 +389,15 @@ class iTopExtensionsMap
|
||||
// to this extension
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if ($sModuleVersion == '') {
|
||||
// Provide a default module version since version is mandatory when recording ExtensionInstallation
|
||||
$sModuleVersion = '0.0.1';
|
||||
}
|
||||
$aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['uninstallable'] ??= 'yes';
|
||||
|
||||
if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension)) {
|
||||
// Already inside an extension, let's add this module the list of modules belonging to this extension
|
||||
$this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName;
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
|
||||
} else {
|
||||
// Not already inside a folder containing an 'extension.xml' file
|
||||
$oExtension = null;
|
||||
if ($sParentExtensionId !== null) {
|
||||
$oExtension = $this->aExtensions[$sParentExtensionId] ?? null;
|
||||
}
|
||||
|
||||
if (is_null($oExtension)) {
|
||||
// Not already inside an folder containing an 'extension.xml' file
|
||||
|
||||
// Ignore non-visible modules and auto-select ones, since these are never prompted
|
||||
// as a choice to the end-user
|
||||
@@ -382,6 +421,13 @@ class iTopExtensionsMap
|
||||
$oExtension->sSourceDir = $sSearchDir;
|
||||
$oExtension->bVisible = $bVisible;
|
||||
$this->AddExtension($oExtension);
|
||||
} else {
|
||||
$oExtension->aModules[] = $sModuleName;
|
||||
$oExtension->aModuleVersion[$sModuleName] = $sModuleVersion;
|
||||
$oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
|
||||
|
||||
$this->aExtensions[$sParentExtensionId] = $oExtension;
|
||||
$this->aExtensionsByCode[$oExtension->sCode] = $oExtension;
|
||||
}
|
||||
|
||||
closedir($hDir);
|
||||
@@ -403,10 +449,9 @@ class iTopExtensionsMap
|
||||
/**
|
||||
* Check if some extension contains a module with missing dependencies...
|
||||
* If so, populate the aMissingDepenencies array
|
||||
* @param string $sFromEnvironment
|
||||
* @return void
|
||||
*/
|
||||
protected function CheckDependencies($sFromEnvironment)
|
||||
protected function CheckDependencies()
|
||||
{
|
||||
$aSearchDirs = [];
|
||||
|
||||
@@ -418,7 +463,7 @@ class iTopExtensionsMap
|
||||
$aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs);
|
||||
|
||||
try {
|
||||
$aAllModules = ModuleDiscovery::GetAvailableModules($aSearchDirs, true);
|
||||
ModuleDiscovery::GetAvailableModules($aSearchDirs, true);
|
||||
} catch (MissingDependencyException $e) {
|
||||
// Some modules have missing dependencies
|
||||
// Let's check what is the impact at the "extensions" level
|
||||
@@ -466,11 +511,17 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function MarkAsChosen($sExtensionCode, $bMark = true)
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode == $sExtensionCode) {
|
||||
$oExtension->bMarkedAsChosen = $bMark;
|
||||
break;
|
||||
}
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$oExtension->bMarkedAsChosen = $bMark;
|
||||
}
|
||||
}
|
||||
|
||||
public function MarkAsUninstallable($sExtensionCode, $bMark = true)
|
||||
{
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$oExtension->bUninstallable = $bMark;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,11 +532,11 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function IsMarkedAsChosen($sExtensionCode)
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode == $sExtensionCode) {
|
||||
return $oExtension->bMarkedAsChosen;
|
||||
}
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
return $oExtension->bMarkedAsChosen;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -497,11 +548,9 @@ class iTopExtensionsMap
|
||||
*/
|
||||
protected function SetInstalledVersion($sExtensionCode, $sInstalledVersion)
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode == $sExtensionCode) {
|
||||
$oExtension->sInstalledVersion = $sInstalledVersion;
|
||||
break;
|
||||
}
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$oExtension->sInstalledVersion = $sInstalledVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,7 +576,12 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function LoadChoicesFromDatabase(Config $oConfig)
|
||||
{
|
||||
foreach ($this->LoadInstalledExtensionsFromDatabase($oConfig) as $oExtension) {
|
||||
$aLoadInstalledExtensionsFromDatabase = $this->LoadInstalledExtensionsFromDatabase($oConfig);
|
||||
if (false === $aLoadInstalledExtensionsFromDatabase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($aLoadInstalledExtensionsFromDatabase as $oExtension) {
|
||||
$this->MarkAsChosen($oExtension->sCode);
|
||||
$this->SetInstalledVersion($oExtension->sCode, $oExtension->sVersion);
|
||||
}
|
||||
@@ -585,8 +639,6 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSourceOnly = iTopExtension::SOURCE_REMOTE)
|
||||
{
|
||||
$bChosen = false;
|
||||
|
||||
foreach ($this->GetAllExtensions() as $oExtension) {
|
||||
if (($oExtension->sSource == $sInSourceOnly) &&
|
||||
($oExtension->bMarkedAsChosen == true) &&
|
||||
|
||||
97
setup/feature_removal/DryRemovalRuntimeEnvironment.php
Normal file
97
setup/feature_removal/DryRemovalRuntimeEnvironment.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use MetaModel;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
|
||||
class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
public const DRY_REMOVAL_AUDIT_ENV = "extension-removal";
|
||||
|
||||
protected array $aExtensionsByCode;
|
||||
private bool $bExtensionMapModified = false;
|
||||
|
||||
/**
|
||||
* Toolset for building a run-time environment
|
||||
*
|
||||
* @param string $sEnvironment (e.g. 'test')
|
||||
* @param bool $bAutoCommit (make the target environment directly, or build a temporary one)
|
||||
*/
|
||||
public function __construct($sEnvironment = self::DRY_REMOVAL_AUDIT_ENV, $bAutoCommit = true)
|
||||
{
|
||||
parent::__construct($sEnvironment, $bAutoCommit);
|
||||
$this->aExtensionsByCode = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSourceEnv
|
||||
* @param array $aExtensionCodesToRemove
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function Prepare(string $sSourceEnv, array $aExtensionCodesToRemove)
|
||||
{
|
||||
|
||||
$sEnv = $this->sFinalEnv;
|
||||
$this->aExtensionsByCode = $aExtensionCodesToRemove;
|
||||
//SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
|
||||
$this->Cleanup();
|
||||
SetupUtils::copydir(APPROOT."/data/$sSourceEnv-modules", APPROOT."/data/$sEnv-modules");
|
||||
|
||||
if (count($aExtensionCodesToRemove) > 0) {
|
||||
$this->RemoveExtensionsLocally($aExtensionCodesToRemove);
|
||||
}
|
||||
$oDryRemovalConfig = clone(MetaModel::GetConfig());
|
||||
$oDryRemovalConfig->ChangeModulesPath($sSourceEnv, $this->sFinalEnv);
|
||||
$this->WriteConfigFileSafe($oDryRemovalConfig);
|
||||
}
|
||||
|
||||
private function RemoveExtensionsLocally(array $aExtensionCodes): void
|
||||
{
|
||||
$oExtensionsMap = new \iTopExtensionsMap($this->sFinalEnv);
|
||||
|
||||
foreach ($aExtensionCodes as $sCode) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
$oExtension = $oExtensionsMap->GetFromExtensionCode($sCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$sDir = $oExtension->sSourceDir;
|
||||
\IssueLog::Info(__METHOD__.": remove extension locally", null, [$oExtension->sCode => $sDir]);
|
||||
SetupUtils::rrmdir($sDir);
|
||||
} else {
|
||||
\IssueLog::Warning(__METHOD__." cannot find extensions", null, ['env' => $this->sFinalEnv, 'code' => $sCode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function Cleanup()
|
||||
{
|
||||
$sEnv = $this->sFinalEnv;
|
||||
SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
|
||||
SetupUtils::rrmdir(APPROOT."/data/cache-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/env-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/conf/$sEnv");
|
||||
@unlink(APPROOT."/data/datamodel-$sEnv.xml");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \iTopExtensionsMap|null
|
||||
*/
|
||||
/*protected function GetExtensionMap(): ?iTopExtensionsMap
|
||||
{
|
||||
if (is_null(parent::GetExtensionMap())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->bExtensionMapModified) {
|
||||
$this->bExtensionMapModified = true;
|
||||
foreach ($this->aExtensionsByCode as $sCode) {
|
||||
parent::GetExtensionMap()->RemoveExtension($sCode);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::GetExtensionMap();
|
||||
}*/
|
||||
}
|
||||
55
setup/feature_removal/ModelReflectionSerializer.php
Normal file
55
setup/feature_removal/ModelReflectionSerializer.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use CoreException;
|
||||
use Exception;
|
||||
|
||||
class ModelReflectionSerializer
|
||||
{
|
||||
private static ModelReflectionSerializer $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModelReflectionSerializer
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModelReflectionSerializer();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModelReflectionSerializer $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function GetModelFromEnvironment(string $sEnv): array
|
||||
{
|
||||
\IssueLog::Info(__METHOD__, null, ['env' => $sEnv]);
|
||||
$sPHPExec = trim(\MetaModel::GetConfig()->Get('php_path'));
|
||||
$sOutput = "";
|
||||
$iRes = 0;
|
||||
exec(sprintf("$sPHPExec %s/get_model_reflection.php --env='%s'", __DIR__, $sEnv), $sOutput, $iRes);
|
||||
if ($iRes != 0) {
|
||||
\IssueLog::Error("Cannot get classes", null, ['env' => $sEnv, 'code' => $iRes, "output" => $sOutput]);
|
||||
throw new CoreException("Cannot get classes");
|
||||
}
|
||||
|
||||
$aClasses = json_decode($sOutput[0] ?? null, true);
|
||||
if (false === $aClasses) {
|
||||
\IssueLog::Error("Invalid JSON", null, ["output" => $sOutput]);
|
||||
throw new Exception("cannot get classes");
|
||||
}
|
||||
|
||||
if (!is_array($aClasses)) {
|
||||
\IssueLog::Error("not an array", null, ["classes" => $aClasses]);
|
||||
throw new Exception("cannot get classes");
|
||||
}
|
||||
|
||||
return $aClasses;
|
||||
}
|
||||
}
|
||||
126
setup/feature_removal/SetupAudit.php
Normal file
126
setup/feature_removal/SetupAudit.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use MetaModel;
|
||||
|
||||
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
|
||||
|
||||
class SetupAudit
|
||||
{
|
||||
//file used when present to trigger audit exception when testing specific setups
|
||||
public const GETISSUE_ERROR_MSG_FILE_FORTESTONLY = '.setup_audit_error_msg.txt';
|
||||
|
||||
private string $sEnvBeforeExtensionRemoval;
|
||||
private string $sEnvAfterExtensionRemoval;
|
||||
|
||||
private array $aClassesBeforeRemoval;
|
||||
private array $aClassesAfterRemoval;
|
||||
private array $aRemovedClasses;
|
||||
private array $aFinalClassesRemoved;
|
||||
|
||||
public function __construct(string $sEnvBeforeExtensionRemoval, string $sEnvAfterExtensionRemoval = DryRemovalRuntimeEnvironment::DRY_REMOVAL_AUDIT_ENV)
|
||||
{
|
||||
$this->sEnvBeforeExtensionRemoval = $sEnvBeforeExtensionRemoval;
|
||||
$this->sEnvAfterExtensionRemoval = $sEnvAfterExtensionRemoval;
|
||||
|
||||
$sCurrentEnvt = MetaModel::GetEnvironment();
|
||||
if ($sCurrentEnvt === $this->sEnvBeforeExtensionRemoval) {
|
||||
$this->aClassesBeforeRemoval = MetaModel::GetClasses();
|
||||
} else {
|
||||
$this->aClassesBeforeRemoval = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvBeforeExtensionRemoval);
|
||||
}
|
||||
|
||||
if ($sCurrentEnvt === $this->sEnvAfterExtensionRemoval) {
|
||||
$this->aClassesAfterRemoval = MetaModel::GetClasses();
|
||||
} else {
|
||||
$this->aClassesAfterRemoval = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvAfterExtensionRemoval);
|
||||
}
|
||||
|
||||
$this->aRemovedClasses = [];
|
||||
$this->aFinalClassesRemoved = [];
|
||||
}
|
||||
|
||||
/*public function SetSelectedExtensions(Config $oConfig, array $aSelectedExtensions)
|
||||
{
|
||||
$oExtensionsMap = new \iTopExtensionsMap();
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
|
||||
sort($aSelectedExtensions);
|
||||
$this->aExtensionToRemove = $oExtensionsMap->GetMissingExtensions($aSelectedExtensions);
|
||||
sort($this->aExtensionToRemove);
|
||||
\SetupLog::Info(__METHOD__, null, ['aExtensionToRemove' => $this->aExtensionToRemove]);
|
||||
}*/
|
||||
|
||||
public function GetRemovedClasses(): array
|
||||
{
|
||||
if (count($this->aRemovedClasses) == 0) {
|
||||
if (count($this->aClassesBeforeRemoval) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
if (count($this->aClassesAfterRemoval) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
$aExtensionsNames = array_diff($this->aClassesBeforeRemoval, $this->aClassesAfterRemoval);
|
||||
$this->aRemovedClasses = [];
|
||||
$aClasses = array_values($aExtensionsNames);
|
||||
sort($aClasses);
|
||||
|
||||
foreach ($aClasses as $i => $sClass) {
|
||||
$this->aRemovedClasses[] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
/** test only: return file path that force audit error being raised
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function GetErrorMessageFilePathForTestOnly(): string
|
||||
{
|
||||
return APPROOT."/data/".self::GETISSUE_ERROR_MSG_FILE_FORTESTONLY;
|
||||
}
|
||||
|
||||
public function GetIssues(bool $bThrowExceptionAtFirstIssue = false): array
|
||||
{
|
||||
$sErrorMessageFilePath = self::GetErrorMessageFilePathForTestOnly();
|
||||
if ($bThrowExceptionAtFirstIssue && is_file($sErrorMessageFilePath)) {
|
||||
$sMsg = file_get_contents($sErrorMessageFilePath);
|
||||
throw new \Exception($sMsg);
|
||||
}
|
||||
|
||||
$this->aFinalClassesRemoved = [];
|
||||
|
||||
foreach ($this->GetRemovedClasses() as $sClass) {
|
||||
if (MetaModel::IsAbstract($sClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MetaModel::IsStandaloneClass($sClass)) {
|
||||
$iCount = $this->Count($sClass);
|
||||
$this->aFinalClassesRemoved[$sClass] = $iCount;
|
||||
if ($bThrowExceptionAtFirstIssue && $iCount > 0) {
|
||||
//setup envt: should raise issue ASAP
|
||||
throw new \Exception($sClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aFinalClassesRemoved;
|
||||
}
|
||||
|
||||
private function Count($sClass): int
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sClass", []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
return $oSet->Count();
|
||||
}
|
||||
}
|
||||
41
setup/feature_removal/get_model_reflection.php
Normal file
41
setup/feature_removal/get_model_reflection.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
require_once(dirname(__DIR__, 2).'/approot.inc.php');
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
$sEnv = null;
|
||||
if (isset($argv)) {
|
||||
foreach ($argv as $iArg => $sArg) {
|
||||
if (preg_match('/^--env=(.*)$/', $sArg, $aMatches)) {
|
||||
$sEnv = $aMatches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($sEnv)) {
|
||||
echo "No environment provided (--env) to read datamodel.";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$sConfFile = utils::GetConfigFilePath($sEnv);
|
||||
|
||||
try {
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e->getMessage();
|
||||
echo $e->getTraceAsString();
|
||||
\SetupLog::Error(
|
||||
"Cannot read model from provided environment",
|
||||
null,
|
||||
[
|
||||
'env' => $sEnv,
|
||||
'error' => $e->getMessage(),
|
||||
'stack' => $e->getTraceAsString(),
|
||||
]
|
||||
);
|
||||
echo "Cannot read model from provided environment";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$aClasses = MetaModel::GetClasses();
|
||||
|
||||
echo json_encode($aClasses);
|
||||
@@ -120,10 +120,6 @@ class ModuleDiscovery
|
||||
if (is_null($aArgs) || ! is_array($aArgs)) {
|
||||
throw new ModuleFileReaderException("Error parsing module file args", 0, null, $sFilePath);
|
||||
}
|
||||
if (!array_key_exists('itop_version', $aArgs)) {
|
||||
// Assume 1.0.2
|
||||
$aArgs['itop_version'] = '1.0.2';
|
||||
}
|
||||
foreach (array_keys(self::$m_aModuleArgs) as $sArgName) {
|
||||
if (!array_key_exists($sArgName, $aArgs)) {
|
||||
throw new Exception("Module '$sId': missing argument '$sArgName'");
|
||||
|
||||
@@ -32,6 +32,7 @@ require_once APPROOT."setup/modulediscovery.class.inc.php";
|
||||
require_once APPROOT.'setup/modelfactory.class.inc.php';
|
||||
require_once APPROOT.'setup/compiler.class.inc.php';
|
||||
require_once APPROOT.'setup/extensionsmap.class.inc.php';
|
||||
require_once APPROOT.'setup/AnalyzeInstallation.php';
|
||||
|
||||
define('MODULE_ACTION_OPTIONAL', 1);
|
||||
define('MODULE_ACTION_MANDATORY', 2);
|
||||
@@ -61,7 +62,23 @@ class RunTimeEnvironment
|
||||
* Extensions map of the source environment
|
||||
* @var iTopExtensionsMap
|
||||
*/
|
||||
protected $oExtensionsMap;
|
||||
protected ?iTopExtensionsMap $oExtensionsMap;
|
||||
|
||||
protected function GetExtensionMap(): ?iTopExtensionsMap
|
||||
{
|
||||
return $this->oExtensionsMap;
|
||||
}
|
||||
|
||||
public function InitExtensionMap($aExtraDirs, $oSourceConfig)
|
||||
{
|
||||
// Actually read the modules available for the target environment,
|
||||
// but get the selection from the source environment and finally
|
||||
// mark as (automatically) chosen alll the "remote" modules present in the
|
||||
// target environment (data/<target-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, $aExtraDirs);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolset for building a run-time environment
|
||||
@@ -145,12 +162,12 @@ class RunTimeEnvironment
|
||||
* @return array Array with the following format:
|
||||
* array =>
|
||||
* 'iTop' => array(
|
||||
* 'version_db' => ... (could be empty in case of a fresh install)
|
||||
* 'version_code => ...
|
||||
* 'installed_version' => ... (could be empty in case of a fresh install)
|
||||
* 'available_version => ...
|
||||
* )
|
||||
* <module_name> => array(
|
||||
* 'version_db' => ...
|
||||
* 'version_code' => ...
|
||||
* 'installed_version' => ...
|
||||
* 'available_version' => ...
|
||||
* 'install' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
@@ -168,137 +185,7 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
$aRes = [
|
||||
ROOT_MODULE => [
|
||||
'version_db' => '',
|
||||
'name_db' => '',
|
||||
'version_code' => ITOP_VERSION_FULL,
|
||||
'name_code' => ITOP_APPLICATION,
|
||||
],
|
||||
];
|
||||
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
|
||||
$aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if ($sModuleName == '') {
|
||||
throw new Exception("Missing name for the module: '$sModuleId'");
|
||||
}
|
||||
if ($sModuleVersion == '') {
|
||||
// The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not)
|
||||
//throw new Exception("Missing version for the module: '$sModuleId'");
|
||||
$sModuleVersion = '1.0.0';
|
||||
}
|
||||
|
||||
$sModuleAppVersion = $aModuleInfo['itop_version'];
|
||||
$aModuleInfo['version_db'] = '';
|
||||
$aModuleInfo['version_code'] = $sModuleVersion;
|
||||
|
||||
if (!in_array($sModuleAppVersion, ['1.0.0', '1.0.1', '1.0.2'])) {
|
||||
// This module is NOT compatible with the current version
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is not compatible with the current version of the application',
|
||||
];
|
||||
} elseif ($aModuleInfo['mandatory']) {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_MANDATORY,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
$aRes[$sModuleName] = $aModuleInfo;
|
||||
}
|
||||
|
||||
try {
|
||||
$aSelectInstall = [];
|
||||
if (! is_null($oConfig)) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
}
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
}
|
||||
|
||||
// Build the list of installed module (get the latest installation)
|
||||
//
|
||||
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
$iRootId = 0;
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel')) {
|
||||
// Root module, what is its ID ?
|
||||
$iId = (int) $aInstall['id'];
|
||||
if ($iId > $iRootId) {
|
||||
$iRootId = $iId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
//$aInstall['comment']; // unsused
|
||||
$iInstalled = strtotime($aInstall['installed']);
|
||||
$sModuleName = $aInstall['name'];
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$sModuleName = ROOT_MODULE;
|
||||
} elseif ($aInstall['parent_id'] != $iRootId) {
|
||||
// Skip all modules belonging to previous installations
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, $aInstallByModule)) {
|
||||
if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$aRes[$sModuleName]['version_db'] = $sModuleVersion;
|
||||
$aRes[$sModuleName]['name_db'] = $aInstall['name'];
|
||||
}
|
||||
|
||||
$aInstallByModule[$sModuleName]['installed'] = $iInstalled;
|
||||
$aInstallByModule[$sModuleName]['version'] = $sModuleVersion;
|
||||
}
|
||||
|
||||
// Adjust the list of proposed modules
|
||||
//
|
||||
foreach ($aInstallByModule as $sModuleName => $aModuleDB) {
|
||||
if ($sModuleName == ROOT_MODULE) {
|
||||
continue;
|
||||
} // Skip the main module
|
||||
|
||||
if (!array_key_exists($sModuleName, $aRes)) {
|
||||
// A module was installed, it is not proposed in the new build... skip
|
||||
continue;
|
||||
}
|
||||
$aRes[$sModuleName]['version_db'] = $aModuleDB['version'];
|
||||
|
||||
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY) {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
return AnalyzeInstallation::GetInstance()->AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,6 +231,7 @@ class RunTimeEnvironment
|
||||
*/
|
||||
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)");
|
||||
@@ -360,30 +248,29 @@ class RunTimeEnvironment
|
||||
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
|
||||
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
|
||||
|
||||
$aRet = [];
|
||||
|
||||
// Determine the installed modules and extensions
|
||||
//
|
||||
$oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
|
||||
$oSourceEnv = new RunTimeEnvironment($sSourceEnv);
|
||||
$aAvailableModules = $oSourceEnv->AnalyzeInstallation($oSourceConfig, $aDirsToCompile);
|
||||
$aAvailableModules = $this->AnalyzeInstallation($oSourceConfig, $aDirsToCompile);
|
||||
|
||||
// Actually read the modules available for the target environment,
|
||||
// but get the selection from the source environment and finally
|
||||
// mark as (automatically) chosen alll the "remote" modules present in the
|
||||
// mark as (automatically) chosen all the "remote" modules present in the
|
||||
// target environment (data/<target-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, $aExtraDirs);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
$this->InitExtensionMap($aExtraDirs, $oSourceConfig);
|
||||
$this->GetExtensionMap()->LoadChoicesFromDatabase($oSourceConfig);
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if ($this->IsExtensionSelected($oExtension)) {
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
$this->GetExtensionMap()->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Do load the required modules
|
||||
//
|
||||
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
|
||||
|
||||
$aRet = [];
|
||||
$aRet[$oDictModule->GetName()] = $oDictModule;
|
||||
|
||||
$oFactory = new ModelFactory($aDirsToCompile);
|
||||
@@ -401,10 +288,9 @@ class RunTimeEnvironment
|
||||
$aModules = $oFactory->FindModules();
|
||||
foreach ($aModules as $oModule) {
|
||||
$sModule = $oModule->GetName();
|
||||
$sModuleRootDir = $oModule->GetRootDir();
|
||||
$bIsExtra = $this->oExtensionsMap->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
|
||||
$bIsExtra = $this->GetExtensionMap()->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
|
||||
if (array_key_exists($sModule, $aAvailableModules)) {
|
||||
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) { //Extra modules are always unless they are 'AutoSelect'
|
||||
if (($aAvailableModules[$sModule]['installed_version'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) { //Extra modules are always unless they are 'AutoSelect'
|
||||
$aRet[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
}
|
||||
@@ -648,7 +534,7 @@ class RunTimeEnvironment
|
||||
$oInstallRec->Set('comment', json_encode($aData));
|
||||
$oInstallRec->Set('parent_id', 0); // root module
|
||||
$oInstallRec->Set('installed', $iInstallationTime);
|
||||
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
|
||||
// Record main installation
|
||||
$oInstallRec = new ModuleInstallation();
|
||||
@@ -661,7 +547,6 @@ class RunTimeEnvironment
|
||||
|
||||
// Record installed modules and extensions
|
||||
//
|
||||
$aAvailableExtensions = [];
|
||||
$aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir());
|
||||
foreach ($aSelectedModuleCodes as $sModuleId) {
|
||||
if (!array_key_exists($sModuleId, $aAvailableModules)) {
|
||||
@@ -669,7 +554,7 @@ class RunTimeEnvironment
|
||||
}
|
||||
$aModuleData = $aAvailableModules[$sModuleId];
|
||||
$sName = $sModuleId;
|
||||
$sVersion = $aModuleData['version_code'];
|
||||
$sVersion = $aModuleData['available_version'];
|
||||
$sUninstallable = $aModuleData['uninstallable'] ?? 'yes';
|
||||
$aComments = [];
|
||||
$aComments[] = $sShortComment;
|
||||
@@ -702,16 +587,17 @@ class RunTimeEnvironment
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
}
|
||||
|
||||
if ($this->oExtensionsMap) {
|
||||
if ($this->GetExtensionMap()) {
|
||||
// Mark as chosen the selected extensions code passed to us
|
||||
|
||||
// Note: some other extensions may already be marked as chosen
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) {
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
$this->GetExtensionMap()->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->oExtensionsMap->GetChoices() as $oExtension) {
|
||||
foreach ($this->GetExtensionMap()->GetChoices() as $oExtension) {
|
||||
$oInstallRec = new ExtensionInstallation();
|
||||
$oInstallRec->Set('code', $oExtension->sCode);
|
||||
$oInstallRec->Set('label', $oExtension->sLabel);
|
||||
@@ -739,9 +625,7 @@ class RunTimeEnvironment
|
||||
public function GetApplicationVersion(Config $oConfig)
|
||||
{
|
||||
try {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$sSQLQuery = "SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install";
|
||||
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
|
||||
$aSelectInstall = ModuleInstallationService::GetInstance()->ReadFromDB($oConfig);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
$this->log_error('Can not connect to the database: host: '.$oConfig->Get('db_host').', user:'.$oConfig->Get('db_user').', pwd:'.$oConfig->Get('db_pwd').', db name:'.$oConfig->Get('db_name'));
|
||||
@@ -975,7 +859,7 @@ class RunTimeEnvironment
|
||||
{
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules)) {
|
||||
$aArgs = [MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code']];
|
||||
$aArgs = [MetaModel::GetConfig(), $aModule['installed_version'], $aModule['available_version']];
|
||||
RunTimeEnvironment::CallInstallerHandler($aAvailableModules[$sModuleId], $sHandlerName, $aArgs);
|
||||
}
|
||||
}
|
||||
@@ -1039,7 +923,7 @@ class RunTimeEnvironment
|
||||
$sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']);
|
||||
// Load data only for selected AND newly installed modules
|
||||
if (in_array($sModuleId, $aSelectedModules)) {
|
||||
if ($aModule['version_db'] != '') {
|
||||
if ($aModule['installed_version'] != '') {
|
||||
// Simulate the load of the previously loaded XML files to get the mapping of the keys
|
||||
if ($bSampleData) {
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
|
||||
@@ -1555,17 +1555,8 @@ JS
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WizardController $oWizard
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if ommitted
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function AnalyzeInstallation($oWizard, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
public static function GetConfig($oWizard)
|
||||
{
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
$oConfig = new Config();
|
||||
$sSourceDir = $oWizard->GetParameter('source_dir', '');
|
||||
|
||||
@@ -1580,7 +1571,25 @@ JS
|
||||
$aParamValues = $oWizard->GetParamForConfigArray();
|
||||
$aParamValues['source_dir'] = $sRelativeSourceDir;
|
||||
$oConfig->UpdateFromParams($aParamValues, null);
|
||||
$aDirsToScan = [$sSourceDir];
|
||||
|
||||
return $oConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WizardController $oWizard
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if ommitted
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function AnalyzeInstallation($oWizard, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
|
||||
$oConfig = self::GetConfig($oWizard);
|
||||
|
||||
$aDirsToScan = [$oWizard->GetParameter('source_dir', '')];
|
||||
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToScan[] = APPROOT.'extensions';
|
||||
|
||||
@@ -50,6 +50,7 @@ require_once(APPROOT.'setup/applicationinstaller.class.inc.php');
|
||||
require_once(APPROOT.'setup/parameters.class.inc.php');
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
require_once(APPROOT.'setup/extensionsmap.class.inc.php');
|
||||
require_once APPROOT.'setup/feature_removal/SetupAudit.php';
|
||||
|
||||
/**
|
||||
* First step of the iTop Installation Wizard: Welcome screen, requirements
|
||||
@@ -1367,6 +1368,52 @@ class WizStepModulesChoice extends WizardStep
|
||||
return ['WizStepModulesChoice', 'WizStepSummary'];
|
||||
}
|
||||
|
||||
public function GetAddedAndRemovedExtensions($aSelectedExtensions)
|
||||
{
|
||||
$aExtensionsAdded = [];
|
||||
$aExtensionsRemoved = [];
|
||||
$aExtensionsNotUninstallable = [];
|
||||
foreach ($this->oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
/* @var \iTopExtension $oExtension */
|
||||
$bSelected = in_array($oExtension->sCode, $aSelectedExtensions);
|
||||
if ($oExtension->bInstalled && !$bSelected) {
|
||||
$aExtensionsRemoved[$oExtension->sCode] = $oExtension->sLabel;
|
||||
if (!$oExtension->CanBeUninstalled()) {
|
||||
$aExtensionsNotUninstallable[$oExtension->sCode] = true;
|
||||
}
|
||||
} elseif (!$oExtension->bInstalled && $bSelected) {
|
||||
$aExtensionsAdded[$oExtension->sCode] = $oExtension->sLabel;
|
||||
}
|
||||
}
|
||||
|
||||
$sExtensionsAdded = '';
|
||||
if (count($aExtensionsAdded) > 0) {
|
||||
$sExtensionsAdded = '<ul>';
|
||||
foreach ($aExtensionsAdded as $sExtensionCode) {
|
||||
$sExtensionsAdded .= '<li>'.$sExtensionCode.'</li>';
|
||||
}
|
||||
$sExtensionsAdded .= '</ul>';
|
||||
} else {
|
||||
$sExtensionsAdded = '<ul><li>No extension added.</li></ul>';
|
||||
}
|
||||
|
||||
$sExtensionsRemoved = '';
|
||||
if (count($aExtensionsRemoved) > 0) {
|
||||
$sExtensionsRemoved = '<ul>';
|
||||
foreach ($aExtensionsRemoved as $sCode => $sExtensionCode) {
|
||||
$sForcedUninstall = '';
|
||||
if (isset($aExtensionsNotUninstallable[$sCode])) {
|
||||
$sForcedUninstall = ' (forced uninstallation)';
|
||||
}
|
||||
$sExtensionsRemoved .= '<li>'.$sExtensionCode.$sForcedUninstall.'</li>';
|
||||
}
|
||||
$sExtensionsRemoved .= '</ul>';
|
||||
} else {
|
||||
$sExtensionsRemoved = '<ul><li>No extension removed.</li></ul>';
|
||||
}
|
||||
return [$sExtensionsAdded, $sExtensionsRemoved];
|
||||
}
|
||||
|
||||
public function ProcessParams($bMoveForward = true)
|
||||
{
|
||||
// Accumulates the selected modules:
|
||||
@@ -1396,9 +1443,14 @@ class WizStepModulesChoice extends WizardStep
|
||||
if (class_exists('CreateITILProfilesInstaller')) {
|
||||
$this->oWizard->SetParameter('old_addon', true);
|
||||
}
|
||||
|
||||
[$sExtensionsAdded, $sExtensionsRemoved] = $this->GetAddedAndRemovedExtensions($aExtensions);
|
||||
|
||||
$this->oWizard->SetParameter('selected_modules', json_encode(array_keys($aModules)));
|
||||
$this->oWizard->SetParameter('selected_extensions', json_encode($aExtensions));
|
||||
$this->oWizard->SetParameter('display_choices', $sDisplayChoices);
|
||||
$this->oWizard->SetParameter('extensions_added', $sExtensionsAdded);
|
||||
$this->oWizard->SetParameter('extensions_removed', $sExtensionsRemoved);
|
||||
return ['class' => 'WizStepSummary', 'state' => ''];
|
||||
}
|
||||
|
||||
@@ -1561,7 +1613,7 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
$aAlternatives = isset($aInfo['alternatives']) ? $aInfo['alternatives'] : [];
|
||||
$aAlternatives = $aInfo['alternatives'] ?? [];
|
||||
$sChoiceName = null;
|
||||
foreach ($aAlternatives as $index => $aChoice) {
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
@@ -1605,7 +1657,7 @@ EOF
|
||||
if ($this->bUpgrade) {
|
||||
// In upgrade mode, the defaults are the installed modules
|
||||
foreach ($aChoice['modules'] as $sModuleId) {
|
||||
if ($aModules[$sModuleId]['version_db'] != '') {
|
||||
if ($aModules[$sModuleId]['installed_version'] != '') {
|
||||
// A module corresponding to this choice is installed
|
||||
$aScores[$sChoiceId][$sModuleId] = true;
|
||||
}
|
||||
@@ -1663,7 +1715,7 @@ EOF
|
||||
}
|
||||
if (array_key_exists('modules', $aChoice)) {
|
||||
foreach ($aChoice['modules'] as $sModuleId) {
|
||||
if ($aModules[$sModuleId]['version_db'] != '') {
|
||||
if ($aModules[$sModuleId]['installed_version'] != '') {
|
||||
// A module corresponding to this choice is installed, increase the score of this choice
|
||||
if (!isset($aScores[$sChoiceId])) {
|
||||
$aScores[$sChoiceId] = [];
|
||||
@@ -1952,10 +2004,47 @@ EOF
|
||||
return '<i class="setup-extension--icon '.$sDecorationClass.'" data-tooltip-content="'.$sResult.'"></i>';
|
||||
}
|
||||
|
||||
public function ComputeChoiceFlags(array $aChoice, string $sChoiceId, array $aSelectedComponents, bool $bAllDisabled, bool $bDisableUninstallCheck, bool $bUpgradeMode)
|
||||
{
|
||||
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
|
||||
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] === true || $aChoice['uninstallable'] === 'yes' : $oITopExtension->CanBeUninstalled();
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $bUpgradeMode && $oITopExtension->bInstalled && !$bCanBeUninstalled && !$bDisableUninstallCheck;
|
||||
|
||||
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
|
||||
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
|
||||
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
|
||||
$bChecked = $bMandatory || $bSelected;
|
||||
|
||||
if (isset($aChoice['sub_options'])) {
|
||||
$aOptions = $aChoice['sub_options']['options'] ?? [];
|
||||
foreach ($aOptions as $index => $aSubChoice) {
|
||||
$sSubChoiceId = $sChoiceId.self::$SEP.$index;
|
||||
$aSubFlags = $this->ComputeChoiceFlags($aSubChoice, $sSubChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $bUpgradeMode);
|
||||
if ($aSubFlags['checked']) {
|
||||
$bChecked = true;
|
||||
if ($aSubFlags['disabled']) {
|
||||
//If some sub options are enabled and cannot be disabled, this choice should also cannot be disabled since it would disable all its sub options
|
||||
$bDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'uninstallable' => $bCanBeUninstalled,
|
||||
'missing' => $bMissingFromDisk,
|
||||
'installed' => $bInstalled,
|
||||
'disabled' => $bDisabled,
|
||||
'checked' => $bChecked,
|
||||
];
|
||||
}
|
||||
|
||||
protected function DisplayOptions($oPage, $aStepInfo, $aSelectedComponents, $aDefaults, $sParentId = '', $bAllDisabled = false)
|
||||
{
|
||||
$aOptions = isset($aStepInfo['options']) ? $aStepInfo['options'] : [];
|
||||
$aAlternatives = isset($aStepInfo['alternatives']) ? $aStepInfo['alternatives'] : [];
|
||||
$aOptions = $aStepInfo['options'] ?? [];
|
||||
$aAlternatives = $aStepInfo['alternatives'] ?? [];
|
||||
|
||||
$bDisableUninstallCheck = (bool)$this->oWizard->GetParameter('force-uninstall', false);
|
||||
|
||||
@@ -1963,43 +2052,33 @@ EOF
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$bIsDefault = array_key_exists($sChoiceId, $aDefaults);
|
||||
|
||||
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
|
||||
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] : $oITopExtension->CanBeUninstalled();
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $this->bUpgrade && $bIsDefault && !$bCanBeUninstalled && !$bDisableUninstallCheck;
|
||||
;
|
||||
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
|
||||
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
|
||||
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
|
||||
$bChecked = $bMandatory || $bSelected;
|
||||
$aFlags = static::ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade);
|
||||
|
||||
$sTooltip = '';
|
||||
$sUnremovable = '';
|
||||
if ($bMissingFromDisk) {
|
||||
if ($aFlags['missing']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag removed">source removed</span>';
|
||||
}
|
||||
if ($bInstalled) {
|
||||
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>';
|
||||
} else {
|
||||
$sTooltip .= '<span class="setup-extension-tag checked tobeinstalled">to be installed</span>';
|
||||
$sTooltip .= '<span class="setup-extension-tag unchecked notinstalled">not installed</span>';
|
||||
}
|
||||
if (!$bCanBeUninstalled) {
|
||||
if (!$aFlags['uninstallable']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag notuninstallable">cannot be uninstalled</span>';
|
||||
}
|
||||
if ($bDisabled && !$bChecked && !$bCanBeUninstalled && !$bDisableUninstallCheck) {
|
||||
if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) {
|
||||
$this->bCanMoveForward = false;//Disable "Next"
|
||||
}
|
||||
$sChecked = $bChecked ? ' checked ' : '';
|
||||
$sDisabled = $bDisabled ? ' disabled data-disabled="disabled" ' : '';
|
||||
$sMissingModule = $bMissingFromDisk ? 'setup-extension--missing' : '';
|
||||
$sChecked = $aFlags['checked'] ? ' checked ' : '';
|
||||
$sDisabled = $aFlags['disabled'] ? ' disabled data-disabled="disabled" ' : '';
|
||||
$sMissingModule = $aFlags['missing'] ? 'setup-extension--missing' : '';
|
||||
|
||||
$sHiddenInput = $bDisabled && $bChecked ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
|
||||
$sHiddenInput = $aFlags['disabled'] && $aFlags['checked'] ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
|
||||
$oPage->add('<div class="choice '.$sMissingModule.'" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.' ');
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $sTooltip);
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $aFlags['disabled'], $sTooltip);
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
$sChoiceName = null;
|
||||
@@ -2034,7 +2113,6 @@ EOF
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
|
||||
if ($sChoiceName == null) {
|
||||
$sChoiceName = $sChoiceId; // All radios share the same name
|
||||
}
|
||||
@@ -2114,6 +2192,18 @@ class WizStepSummary extends WizardStep
|
||||
$this->bDependencyCheck = true;
|
||||
try {
|
||||
SetupUtils::AnalyzeInstallation($this->oWizard, true, $aSelectedModules);
|
||||
|
||||
/*$sInstallMode = utils::ReadParam('install_mode');
|
||||
\SetupLog::Info(__METHOD__, null, ['$sInstallMode' => $sInstallMode]);
|
||||
//if ($sInstallMode === "upgrade") {
|
||||
$aExtensions = json_decode($this->oWizard->GetParameter('selected_extensions'), true);
|
||||
$oSetupAudit = new SetupAudit([]);
|
||||
|
||||
$oConfig = SetupUtils::GetConfig($this->oWizard);
|
||||
$oSetupAudit->SetSelectedExtensions($oConfig, $aExtensions);
|
||||
//$oSetupAudit->AuditExtensionsCleanupRules(true);
|
||||
//}
|
||||
*/
|
||||
} catch (MissingDependencyException $e) {
|
||||
$this->bDependencyCheck = false;
|
||||
$this->sDependencyIssue = $e->getHtmlDesc();
|
||||
@@ -2178,6 +2268,14 @@ class WizStepSummary extends WizardStep
|
||||
|
||||
$oPage->add('<fieldset id="summary"><legend>Installation Parameters</legend>');
|
||||
$oPage->add('<div id="params_summary">');
|
||||
|
||||
$oPage->add('<div class="closed"><span class="title ibo-setup-summary-title">Extensions to be installed</span>');
|
||||
$oPage->add($this->oWizard->GetParameter('extensions_added'));
|
||||
$oPage->add('</div>');
|
||||
$oPage->add('<div class="closed"><span class="title ibo-setup-summary-title">Extensions to be uninstalled</span>');
|
||||
$oPage->add($this->oWizard->GetParameter('extensions_removed'));
|
||||
$oPage->add('</div>');
|
||||
|
||||
$oPage->add('<div class="closed"><span class="title ibo-setup-summary-title">Database Parameters</span><ul>');
|
||||
$oPage->add('<li>Server Name: '.$aInstallParams['database']['server'].'</li>');
|
||||
$oPage->add('<li>DB User Name: '.$aInstallParams['database']['user'].'</li>');
|
||||
|
||||
@@ -1019,10 +1019,10 @@ EOF
|
||||
if ($sModuleId == '_Root_') {
|
||||
continue;
|
||||
}
|
||||
if ($aModuleData['version_db'] == '') {
|
||||
if ($aModuleData['installed_version'] == '') {
|
||||
continue;
|
||||
}
|
||||
$oPage->add('InstalledModule/'.$sModuleId.': '.$aModuleData['version_db']."\n");
|
||||
$oPage->add('InstalledModule/'.$sModuleId.': '.$aModuleData['installed_version']."\n");
|
||||
}
|
||||
|
||||
$oPage->add('===== end =====');
|
||||
|
||||
@@ -54,10 +54,20 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
*/
|
||||
abstract public function GetDatamodelDeltaAbsPath(): string;
|
||||
|
||||
/**
|
||||
* @return array<string, string> : dict extensions folders by their code
|
||||
*/
|
||||
public function GetAdditionalFeaturePaths(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::LoadRequiredItopFiles();
|
||||
$this->oEnvironment = new UnitTestRunTimeEnvironment('production', $this->GetTestEnvironment());
|
||||
if (is_null($this->oEnvironment)) {
|
||||
$this->oEnvironment = new UnitTestRunTimeEnvironment($this->GetTestEnvironment());
|
||||
}
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
@@ -123,7 +133,6 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
// Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once.
|
||||
// This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457
|
||||
if (false === $this->IsEnvironmentReady()) {
|
||||
|
||||
$this->debug("Preparing custom environment '$sTestEnv' with the following datamodel files:");
|
||||
foreach ($this->oEnvironment->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
|
||||
$this->debug(" - $sCustomDatamodelFile");
|
||||
@@ -155,24 +164,33 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
$oTestConfig->ChangeModulesPath($sSourceEnv, $sTestEnv);
|
||||
// - Switch DB name to a dedicated one so we don't mess with the original one
|
||||
$sTestEnvSanitizedForDBName = preg_replace('/[^\d\w]/', '', $sTestEnv);
|
||||
$oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName);
|
||||
$sPreviousDB = $oTestConfig->Get('db_name');
|
||||
$sNewDB = $sPreviousDB.'_'.$sTestEnvSanitizedForDBName;
|
||||
$oTestConfig->Set('db_name', $sNewDB);
|
||||
|
||||
// - Compile env. based on the existing 'production' env.
|
||||
$oEnvironment = new UnitTestRunTimeEnvironment($sSourceEnv, $sTestEnv);
|
||||
$oEnvironment->WriteConfigFileSafe($oTestConfig);
|
||||
$oEnvironment->CompileFrom($sSourceEnv);
|
||||
//$oEnvironment = new UnitTestRunTimeEnvironment($sSourceEnv, $sTestEnv);
|
||||
$this->oEnvironment->WriteConfigFileSafe($oTestConfig);
|
||||
$this->oEnvironment->CompileFrom($sSourceEnv);
|
||||
|
||||
// - Force re-creating a fresh DB
|
||||
CMDBSource::InitFromConfig($oTestConfig);
|
||||
if (CMDBSource::IsDB($oTestConfig->Get('db_name'))) {
|
||||
if (CMDBSource::IsDB($sNewDB)) {
|
||||
CMDBSource::DropDB();
|
||||
}
|
||||
CMDBSource::CreateDB($oTestConfig->Get('db_name'));
|
||||
CMDBSource::CreateDB($sNewDB);
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv);
|
||||
// N°7446 For some reason we need to create the DB schema before starting the MM, then only we can create the tables.
|
||||
MetaModel::DBCreate();
|
||||
|
||||
// Make sure that runtime environment is complete
|
||||
// RunTimeEnvironment::AnalyzeInstallation would not return core modules otherwise...
|
||||
CMDBSource::DropTable("priv_module_install");
|
||||
CMDBSource::Query("CREATE TABLE $sNewDB.priv_module_install SELECT * FROM $sPreviousDB.priv_module_install");
|
||||
|
||||
$this->debug("Custom environment '$sTestEnv' is ready!");
|
||||
} else {
|
||||
$this->debug("Custom environment '$sTestEnv' READY BUILT:");
|
||||
}
|
||||
|
||||
parent::PrepareEnvironment();
|
||||
|
||||
@@ -26,26 +26,25 @@ use utils;
|
||||
*/
|
||||
class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
/**
|
||||
* @var false
|
||||
*/
|
||||
public bool $bUseDelta = true;
|
||||
|
||||
/**
|
||||
* @var true
|
||||
*/
|
||||
public bool $bUseAdditionalFeatures = false;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $aCustomDatamodelFiles = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var string[]
|
||||
*/
|
||||
protected $sSourceEnv;
|
||||
|
||||
public function __construct($sSourceEnv, $sTargetEnv)
|
||||
{
|
||||
parent::__construct($sTargetEnv);
|
||||
$this->sSourceEnv = $sSourceEnv;
|
||||
}
|
||||
|
||||
public function GetEnvironment(): string
|
||||
{
|
||||
return $this->sFinalEnv;
|
||||
}
|
||||
protected $aAdditionExtensionFoldersByCode = null;
|
||||
|
||||
public function CompileFrom($sSourceEnv, $bUseSymLinks = null)
|
||||
{
|
||||
@@ -56,6 +55,15 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
|
||||
SetupUtils::copydir(APPROOT.'/data/'.$sSourceEnv.'-modules', $sDestModulesDir, $bUseSymLinks);
|
||||
|
||||
if ($this->bUseAdditionalFeatures) {
|
||||
foreach ($this->GetExtensionFoldersToAdd() as $sExtensionCode => $sFolderPath) {
|
||||
\SetupLog::Info("ExtensionFoldersToAdd: $sExtensionCode => $sFolderPath");
|
||||
$sFolderName = basename($sFolderPath);
|
||||
@mkdir($sDestModulesDir.DIRECTORY_SEPARATOR.$sFolderName);
|
||||
SetupUtils::copydir($sFolderPath, $sDestModulesDir.DIRECTORY_SEPARATOR.$sFolderName, $bUseSymLinks);
|
||||
}
|
||||
}
|
||||
|
||||
parent::CompileFrom($sSourceEnv, $bUseSymLinks);
|
||||
}
|
||||
|
||||
@@ -94,23 +102,43 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
*/
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
{
|
||||
\SetupLog::Info(__METHOD__);
|
||||
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
|
||||
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
|
||||
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
|
||||
$sDeltaName = basename($sDeltaFile);
|
||||
$sDeltaDir = dirname($sDeltaFile);
|
||||
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
|
||||
$aRet[$sDeltaId] = $oDelta;
|
||||
if ($this->bUseDelta) {
|
||||
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
|
||||
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
|
||||
$sDeltaName = basename($sDeltaFile);
|
||||
$sDeltaDir = dirname($sDeltaFile);
|
||||
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
|
||||
$aRet[$sDeltaId] = $oDelta;
|
||||
}
|
||||
}
|
||||
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
public function GetCustomDatamodelFiles()
|
||||
public function GetExtensionFoldersToAdd(): array
|
||||
{
|
||||
if (!is_null($this->aCustomDatamodelFiles)) {
|
||||
return $this->aCustomDatamodelFiles;
|
||||
if (is_null($this->aAdditionExtensionFoldersByCode)) {
|
||||
$this->InitViaItopCustomDatamodelTestCaseClasses();
|
||||
}
|
||||
|
||||
return $this->aAdditionExtensionFoldersByCode;
|
||||
}
|
||||
|
||||
public function GetCustomDatamodelFiles(): array
|
||||
{
|
||||
if (is_null($this->aCustomDatamodelFiles)) {
|
||||
$this->InitViaItopCustomDatamodelTestCaseClasses();
|
||||
}
|
||||
|
||||
return $this->aCustomDatamodelFiles;
|
||||
}
|
||||
|
||||
public function InitViaItopCustomDatamodelTestCaseClasses()
|
||||
{
|
||||
$this->aAdditionExtensionFoldersByCode = [];
|
||||
$this->aCustomDatamodelFiles = [];
|
||||
|
||||
// Search for the PHP files implementing the method GetDatamodelDeltaAbsPath
|
||||
@@ -169,16 +197,19 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
continue;
|
||||
}
|
||||
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
|
||||
if (!is_file($sDeltaFile)) {
|
||||
throw new \Exception("Unknown delta file: $sDeltaFile, from test class '$sClass'");
|
||||
}
|
||||
if (!in_array($sDeltaFile, $this->aCustomDatamodelFiles)) {
|
||||
$this->aCustomDatamodelFiles[] = $sDeltaFile;
|
||||
if (strlen($sDeltaFile) > 0) {
|
||||
if (!is_file($sDeltaFile)) {
|
||||
throw new \Exception("Unknown delta file: $sDeltaFile, from test class '$sClass'");
|
||||
}
|
||||
if (!in_array($sDeltaFile, $this->aCustomDatamodelFiles)) {
|
||||
$this->aCustomDatamodelFiles[] = $sDeltaFile;
|
||||
}
|
||||
}
|
||||
|
||||
$aExtensionsPaths = $oTestClassInstance->GetAdditionalFeaturePaths();
|
||||
$this->aAdditionExtensionFoldersByCode = array_merge($this->aAdditionExtensionFoldersByCode, $aExtensionsPaths);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aCustomDatamodelFiles;
|
||||
}
|
||||
|
||||
private function FindFilesModifiedAfter(float $fReferenceTimestamp, string $sPathToScan, array &$aModifiedFiles)
|
||||
|
||||
@@ -7,30 +7,26 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
|
||||
$MySettings = [
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => 'http://%server(SERVER_NAME)?:localhost%/itop/iTop/',
|
||||
|
||||
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
);
|
||||
$MyModuleSettings = [
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
);
|
||||
?>
|
||||
$MyModules = [
|
||||
];
|
||||
|
||||
@@ -7,30 +7,26 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
|
||||
$MySettings = [
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => 'http://' . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost') . '/itop/iTop/',
|
||||
'app_root_url' => 'http://'.(isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost').'/itop/iTop/',
|
||||
|
||||
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
);
|
||||
$MyModuleSettings = [
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
);
|
||||
?>
|
||||
$MyModules = [
|
||||
];
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use AnalyzeInstallation;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use ModuleInstallationService;
|
||||
|
||||
class AnalyzeInstallationTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/AnalyzeInstallation.php');
|
||||
$this->RequireOnceItopFile('setup/ModuleInstallationService.php');
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/runtimeenv.class.inc.php');
|
||||
}
|
||||
|
||||
public static function AnalyzeInstallationProvider()
|
||||
{
|
||||
//$aModules = json_decode(file_get_contents(__DIR__.'/ressources/priv_modules.json'), true);
|
||||
$aAnalyzeInstallationOutput = json_decode(file_get_contents(__DIR__.'/ressources/analyze_installation_output.json'), true);
|
||||
$aAvailableModules = json_decode(file_get_contents(__DIR__.'/ressources/available_modules.json'), true);
|
||||
return [
|
||||
'new modules not in DB setup history' => [
|
||||
'aAvailableModules' => [
|
||||
'mandatory_module/1.0.0' => [
|
||||
"mandatory" => true,
|
||||
"ga" => "bu",
|
||||
],
|
||||
'optional_module/6.6.6' => [
|
||||
"mandatory" => false,
|
||||
"zo" => "meu",
|
||||
],
|
||||
],
|
||||
'aInstalledModules' => [],
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
'mandatory_module' => [
|
||||
"mandatory" => true,
|
||||
'installed_version' => '',
|
||||
'available_version' => '1.0.0',
|
||||
'install' => [
|
||||
'flag' => 2,
|
||||
'message' => 'the module is part of the application',
|
||||
],
|
||||
"ga" => "bu",
|
||||
],
|
||||
'optional_module' => [
|
||||
"mandatory" => false,
|
||||
'installed_version' => '',
|
||||
'available_version' => '6.6.6',
|
||||
"zo" => "meu",
|
||||
'install' => [
|
||||
'flag' => 1,
|
||||
'message' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'new modules ALREADY in DB setup history' => [
|
||||
'aAvailableModules' => [
|
||||
'mandatory_module/1.0.0' => [
|
||||
"mandatory" => true,
|
||||
"ga" => "bu",
|
||||
],
|
||||
'optional_module/6.6.6' => [
|
||||
"mandatory" => false,
|
||||
"zo" => "meu",
|
||||
],
|
||||
],
|
||||
'aInstalledModules' => json_decode(file_get_contents(__DIR__.'/ressources/priv_modules_simpleusecase.json'), true),
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '3.3.0-dev-svn',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
'mandatory_module' => [
|
||||
"mandatory" => true,
|
||||
'installed_version' => '3.3.0',
|
||||
'available_version' => '1.0.0',
|
||||
"ga" => "bu",
|
||||
'install' => [
|
||||
'flag' => 2,
|
||||
'message' => 'the module is part of the application',
|
||||
],
|
||||
'uninstall' => [
|
||||
'flag' => 3,
|
||||
'message' => 'the module is part of the application',
|
||||
],
|
||||
],
|
||||
'optional_module' => [
|
||||
"mandatory" => false,
|
||||
'installed_version' => '3.3.0',
|
||||
'available_version' => '6.6.6',
|
||||
"zo" => "meu",
|
||||
'install' => [
|
||||
'flag' => 1,
|
||||
'message' => '',
|
||||
],
|
||||
'uninstall' => [
|
||||
'flag' => 1,
|
||||
'message' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'dummyfirst installation' => [
|
||||
'aAvailableModules' => [],
|
||||
'aInstalledModules' => [],
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
],
|
||||
],
|
||||
'dummy 2nd installation' => [
|
||||
'aAvailableModules' => [],
|
||||
'aInstalledModules' => json_decode(file_get_contents(__DIR__.'/ressources/priv_modules2.json'), true),
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '3.3.0-dev-svn',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
],
|
||||
],
|
||||
'real_case' => [
|
||||
'aAvailableModules' => $aAvailableModules,
|
||||
'aInstalledModules' => json_decode(file_get_contents(__DIR__.'/ressources/priv_modules2.json'), true),
|
||||
'expected' => $aAnalyzeInstallationOutput,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider AnalyzeInstallationProvider
|
||||
*/
|
||||
public function testAnalyzeInstallation($aAvailableModules, $aInstalledModules, $expected)
|
||||
{
|
||||
$sContent = str_replace(['ITOP_VERSION_FULL', 'ITOP_APPLICATION'], [ITOP_VERSION_FULL, ITOP_APPLICATION], json_encode($expected));
|
||||
$expected = json_decode($sContent, true);
|
||||
|
||||
$this->SetNonPublicProperty(AnalyzeInstallation::GetInstance(), "aAvailableModules", $aAvailableModules);
|
||||
//$aModules = json_decode(file_get_contents(__DIR__.'/ressources/priv_modules2.json'), true);
|
||||
$this->SetNonPublicProperty(ModuleInstallationService::GetInstance(), "aSelectInstall", $aInstalledModules);
|
||||
|
||||
$oConfig = $this->createMock(\Config::class);
|
||||
|
||||
$modulesPath = [
|
||||
APPROOT.'extensions',
|
||||
];
|
||||
$aModules = AnalyzeInstallation::GetInstance()->AnalyzeInstallation($oConfig, $modulesPath, false, null);
|
||||
$this->assertEquals($expected, $aModules);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class WizStepModulesChoiceFake extends WizStepModulesChoice
|
||||
{
|
||||
public function __construct(WizardController $oWizard, $sCurrentState)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function setExtensionMap(iTopExtensionsMap $oMap)
|
||||
{
|
||||
$this->oExtensionsMap = $oMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use ItopExtensionsMap;
|
||||
use iTopExtensionsMapFake;
|
||||
use ModuleDiscovery;
|
||||
use WizardController;
|
||||
|
||||
class WizStepModulesChoiceTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('/setup/unattended-install/InstallationFileService.php');
|
||||
require_once __DIR__.'/iTopExtensionsMapFake.php';
|
||||
require_once __DIR__.'/WizStepModulesChoiceFake.php';
|
||||
|
||||
$this->oStep = new \WizStepModulesChoiceFake(new WizardController('', ''), '');
|
||||
ModuleDiscovery::ResetCache();
|
||||
}
|
||||
|
||||
public function ProviderComputeChoiceFlags()
|
||||
{
|
||||
return [
|
||||
'selected but not installed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => false,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => ['_0' => '_0'],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => false,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'not selected, not installed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => false,
|
||||
'checked' => false,
|
||||
],
|
||||
],
|
||||
'installed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => true,
|
||||
'disabled' => false,
|
||||
'checked' => false,
|
||||
],
|
||||
],
|
||||
'installed non uninstallable extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => false,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => false,
|
||||
'missing' => false,
|
||||
'installed' => true,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'mandatory extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => true,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'optional sub extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext1-1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
'sub_options' => [
|
||||
'options' => [
|
||||
[
|
||||
'extension_code' => 'itop-ext1-1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => false,
|
||||
'checked' => false,
|
||||
],
|
||||
],
|
||||
'mandatory sub extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext1-1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
'sub_options' => [
|
||||
'options' => [
|
||||
[
|
||||
'extension_code' => 'itop-ext1-1',
|
||||
'mandatory' => true,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'non uninstallable sub extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
'itop-ext1-1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
'sub_options' => [
|
||||
'options' => [
|
||||
[
|
||||
'extension_code' => 'itop-ext1-1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => true,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ProviderComputeChoiceFlags
|
||||
*/
|
||||
public function testComputeChoiceFlags($aExtensions, $bUpgrade, $bDisableUninstallCheck, $sChoiceId, $aStepInfo, $aSelected, $aExpectedFlags)
|
||||
{
|
||||
$this->oStep->setExtensionMap(iTopExtensionsMapFake::createFromArray($aExtensions));
|
||||
$aFlags = $this->oStep->ComputeChoiceFlags($aStepInfo, $sChoiceId, $aSelected, false, $bDisableUninstallCheck, $bUpgrade);
|
||||
$this->assertEquals($aExpectedFlags, $aFlags);
|
||||
}
|
||||
|
||||
public function ProviderGetAddedAndRemovedExtensions()
|
||||
{
|
||||
return [
|
||||
'no extensions' => [
|
||||
'aExtensions' => [],
|
||||
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'no extensions selected' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'no extensions removed' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'aSelected' => ['itop-ext1'],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'One added extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'aSelected' => ['itop-ext1'],
|
||||
'sExpectedAddedList' => '<ul><li>itop-ext1</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'One removed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>itop-ext1</li></ul>',
|
||||
],
|
||||
'Forced removed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
'uninstallable' => false,
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>itop-ext1 (forced uninstallation)</li></ul>',
|
||||
],
|
||||
'added and removed extensions' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext-added1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext-added2' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext-removed1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
'itop-ext-removed2' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'aSelected' => ['itop-ext-added1', 'itop-ext-added2'],
|
||||
'sExpectedAddedList' => '<ul><li>itop-ext-added1</li><li>itop-ext-added2</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>itop-ext-removed1</li><li>itop-ext-removed2</li></ul>',
|
||||
],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ProviderGetAddedAndRemovedExtensions
|
||||
*/
|
||||
public function testGetAddedAndRemovedExtensions($aExtensions, $aSelectedExtensions, $sExpectedAddedList, $sExpectedRemovedList)
|
||||
{
|
||||
$this->oStep->setExtensionMap(iTopExtensionsMapFake::createFromArray($aExtensions));
|
||||
[$sAddedList, $sRemovedList] = $this->oStep->GetAddedAndRemovedExtensions($aSelectedExtensions);
|
||||
$this->assertEquals($sExpectedAddedList, $sAddedList);
|
||||
$this->assertEquals($sExpectedRemovedList, $sRemovedList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup\FeatureRemoval;
|
||||
|
||||
use Combodo\iTop\Setup\FeatureRemoval\ModelReflectionSerializer;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
class ModelSerializationTest extends ItopDataTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('/setup/feature_removal/ModelReflectionSerializer.php');
|
||||
}
|
||||
|
||||
public function testGetModelFromEnvironment()
|
||||
{
|
||||
$aModel = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->GetTestEnvironment());
|
||||
$this->assertEqualsCanonicalizing(MetaModel::GetClasses(), $aModel);
|
||||
}
|
||||
|
||||
public function testGetModelFromEnvironmentFailure()
|
||||
{
|
||||
$this->expectException(\CoreException::class);
|
||||
$this->expectExceptionMessage("Cannot get classes");
|
||||
ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment('gabuzomeu');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup\FeatureRemoval;
|
||||
|
||||
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
|
||||
use Exception;
|
||||
|
||||
class SetupAuditTest extends ItopCustomDatamodelTestCase
|
||||
{
|
||||
public const ENVT = 'php-unit-extensionremoval-tests';
|
||||
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
//no delta: empty path provided
|
||||
return "";
|
||||
}
|
||||
|
||||
public function GetAdditionalFeaturePaths(): array
|
||||
{
|
||||
$aFeaturePaths = [];
|
||||
foreach (glob(__DIR__."/additional_features/*", GLOB_ONLYDIR) as $aFeaturePath) {
|
||||
$sCode = basename($aFeaturePath);
|
||||
$aFeaturePaths[$sCode] = $aFeaturePath;
|
||||
}
|
||||
|
||||
return $aFeaturePaths;
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::LoadRequiredItopFiles();
|
||||
$this->oEnvironment = new UnitTestRunTimeEnvironment(self::ENVT);
|
||||
$this->oEnvironment->bUseDelta = false;
|
||||
$this->oEnvironment->bUseAdditionalFeatures = true;
|
||||
parent::setUp();
|
||||
|
||||
$this->RequireOnceItopFile('/setup/feature_removal/SetupAudit.php');
|
||||
$this->RequireOnceItopFile('/setup/feature_removal/DryRemovalRuntimeEnvironment.php');
|
||||
}
|
||||
|
||||
public function GetTestEnvironment(): string
|
||||
{
|
||||
return self::ENVT;
|
||||
}
|
||||
|
||||
public function testComputeDryRemoval()
|
||||
{
|
||||
$oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment();
|
||||
$oDryRemovalRuntimeEnvt->Prepare($this->GetTestEnvironment(), ['nominal_ext1', 'finalclass_ext2']);
|
||||
$oDryRemovalRuntimeEnvt->CompileFrom($this->GetTestEnvironment());
|
||||
|
||||
$oSetupAudit = new SetupAudit(\MetaModel::GetEnvironment());
|
||||
|
||||
$expected = [
|
||||
"Feature1Module1MyClass",
|
||||
"FinalClassFeature2Module1MyClass",
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation",
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetRemovedClasses());
|
||||
|
||||
$expected = [
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation" => 0,
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetIssues());
|
||||
}
|
||||
|
||||
public function testGetIssues()
|
||||
{
|
||||
$sUID = "AuditExtensionsCleanupRules_".uniqid();
|
||||
$oOrg = $this->CreateOrganization($sUID);
|
||||
$this->createObject('FinalClassFeature1Module1MyFinalClassFromLocation', ['org_id' => $oOrg->GetKey(), 'name' => $sUID, 'name2' => uniqid()]);
|
||||
|
||||
$oSetupAudit = new SetupAudit(\MetaModel::GetEnvironment());
|
||||
$aRemovedClasses = [
|
||||
"Feature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyFinalClassFromLocation",
|
||||
"FinalClassFeature2Module1MyClass",
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation",
|
||||
];
|
||||
|
||||
//avoid setup dry computation
|
||||
$this->SetNonPublicProperty($oSetupAudit, 'aRemovedClasses', $aRemovedClasses);
|
||||
|
||||
$expected = [
|
||||
"FinalClassFeature1Module1MyFinalClassFromLocation" => 1,
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation" => 0,
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetIssues());
|
||||
}
|
||||
|
||||
public function testAuditExtensionsCleanupRulesFailASAP()
|
||||
{
|
||||
$sUID = "AuditExtensionsCleanupRules_".uniqid();
|
||||
$oOrg = $this->CreateOrganization($sUID);
|
||||
$this->createObject('FinalClassFeature1Module1MyFinalClassFromLocation', ['org_id' => $oOrg->GetKey(), 'name' => $sUID, 'name2' => uniqid()]);
|
||||
$this->createObject('FinalClassFeature2Module1MyFinalClassFromLocation', ['org_id' => $oOrg->GetKey(), 'name' => $sUID, 'name2' => uniqid()]);
|
||||
|
||||
$oSetupAudit = new SetupAudit(\MetaModel::GetEnvironment());
|
||||
$aRemovedClasses = [
|
||||
"Feature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyFinalClassFromLocation",
|
||||
"FinalClassFeature2Module1MyClass",
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation",
|
||||
];
|
||||
|
||||
//avoid setup dry computation
|
||||
$this->SetNonPublicProperty($oSetupAudit, 'aRemovedClasses', $aRemovedClasses);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage('FinalClassFeature1Module1MyFinalClassFromLocation');
|
||||
$oSetupAudit->GetIssues(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension format="1.0">
|
||||
<extension_code>finalclass_ext1</extension_code>
|
||||
<company>Combodo SARL</company>
|
||||
<author><![CDATA[Odain]]></author>
|
||||
<label><![CDATA[Ext For Test]]></label>
|
||||
<description><![CDATA[Ext For Test]]></description>
|
||||
<version>6.6.6</version>
|
||||
<modules type="array">
|
||||
<module>
|
||||
<id>finalclass_ext1_module1</id>
|
||||
<version>tags/6.6.6</version>
|
||||
</module>
|
||||
</modules>
|
||||
<release_date>2023-07-19</release_date>
|
||||
<version_description><![CDATA[
|
||||
]]></version_description>
|
||||
<itop_version_min>3.2.0</itop_version_min>
|
||||
<status></status>
|
||||
<mandatory>false</mandatory>
|
||||
<more_info_url></more_info_url>
|
||||
</extension>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
|
||||
<classes>
|
||||
<class id="FinalClassFeature1Module1MyFinalClassFromLocation" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature1Module1MyFinalClassFromLocation</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name2"/>
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name2"/>
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<order>
|
||||
<columns>
|
||||
<column id="name2" ascending="false"/>
|
||||
</columns>
|
||||
</order>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name2" xsi:type="AttributeString">
|
||||
<sql>name2</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
<parent>Location</parent>
|
||||
</class>
|
||||
<class id="FinalClassFeature1Module1MyClass" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature1Module1MyClass</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<order>
|
||||
<columns>
|
||||
<column id="name" ascending="false"/>
|
||||
</columns>
|
||||
</order>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
</classes>
|
||||
<menus/>
|
||||
<user_rights/>
|
||||
<module_parameters/>
|
||||
</itop_design>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
//// PHP Data Model definition file
|
||||
//
|
||||
//// WARNING - WARNING - WARNING
|
||||
//// DO NOT EDIT THIS FILE (unless you know what you are doing)
|
||||
////
|
||||
//// If you use supply a datamodel.xxxx.xml file with your module
|
||||
//// the this file WILL BE overwritten by the compilation of the
|
||||
//// module (during the setup) if the datamodel.xxxx.xml file
|
||||
//// contains the definition of new classes or menus.
|
||||
////
|
||||
//// The recommended way to define new classes (for iTop 2.0) is via the XML definition.
|
||||
//// This file remains in the module's template only for the cases where there is:
|
||||
//// - either no new class or menu defined in the XML file
|
||||
//// - or no XML file at all supplied by the module
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
//
|
||||
// iTop module definition file
|
||||
//
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'finalclass_ext1_module1/6.6.6',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
'label' => 'Ext For Test',
|
||||
'category' => 'business',
|
||||
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/3.2.0',
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
'installer' => '',
|
||||
|
||||
// Components
|
||||
//
|
||||
'datamodel' => [
|
||||
'model.finalclass_ext1_module1.php',
|
||||
],
|
||||
'webservice' => [],
|
||||
'data.struct' => [// add your 'structure' definition XML files here,
|
||||
],
|
||||
'data.sample' => [// add your sample data XML files here,
|
||||
],
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
|
||||
// Default settings
|
||||
//
|
||||
'settings' => [// Module specific settings go here, if any
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension format="1.0">
|
||||
<extension_code>finalclass_ext2</extension_code>
|
||||
<company>Combodo SARL</company>
|
||||
<author><![CDATA[Odain]]></author>
|
||||
<label><![CDATA[Ext For Test]]></label>
|
||||
<description><![CDATA[Ext For Test]]></description>
|
||||
<version>6.6.6</version>
|
||||
<modules type="array">
|
||||
<module>
|
||||
<id>finalclass_ext2_module1</id>
|
||||
<version>tags/6.6.6</version>
|
||||
</module>
|
||||
</modules>
|
||||
<release_date>2023-07-19</release_date>
|
||||
<version_description><![CDATA[
|
||||
]]></version_description>
|
||||
<itop_version_min>3.2.0</itop_version_min>
|
||||
<status></status>
|
||||
<mandatory>false</mandatory>
|
||||
<more_info_url></more_info_url>
|
||||
</extension>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
|
||||
<classes>
|
||||
<class id="FinalClassFeature2Module1MyFinalClassFromLocation" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature2Module1MyFinalClassFromLocation</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name2"/>
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name2"/>
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<order>
|
||||
<columns>
|
||||
<column id="name2" ascending="false"/>
|
||||
</columns>
|
||||
</order>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name2" xsi:type="AttributeString">
|
||||
<sql>name2</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
<parent>Location</parent>
|
||||
</class>
|
||||
<class id="FinalClassFeature2Module1MyClass" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature2Module1MyClass</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<order>
|
||||
<columns>
|
||||
<column id="name" ascending="false"/>
|
||||
</columns>
|
||||
</order>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
</classes>
|
||||
<menus/>
|
||||
<user_rights/>
|
||||
<module_parameters/>
|
||||
</itop_design>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
//// PHP Data Model definition file
|
||||
//
|
||||
//// WARNING - WARNING - WARNING
|
||||
//// DO NOT EDIT THIS FILE (unless you know what you are doing)
|
||||
////
|
||||
//// If you use supply a datamodel.xxxx.xml file with your module
|
||||
//// the this file WILL BE overwritten by the compilation of the
|
||||
//// module (during the setup) if the datamodel.xxxx.xml file
|
||||
//// contains the definition of new classes or menus.
|
||||
////
|
||||
//// The recommended way to define new classes (for iTop 2.0) is via the XML definition.
|
||||
//// This file remains in the module's template only for the cases where there is:
|
||||
//// - either no new class or menu defined in the XML file
|
||||
//// - or no XML file at all supplied by the module
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
//
|
||||
// iTop module definition file
|
||||
//
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'finalclass_ext2_module1/6.6.6',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
'label' => 'Ext For Test',
|
||||
'category' => 'business',
|
||||
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/3.2.0',
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
'installer' => '',
|
||||
|
||||
// Components
|
||||
//
|
||||
'datamodel' => [
|
||||
'model.finalclass_ext2_module1.php',
|
||||
],
|
||||
'webservice' => [],
|
||||
'data.struct' => [// add your 'structure' definition XML files here,
|
||||
],
|
||||
'data.sample' => [// add your sample data XML files here,
|
||||
],
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
|
||||
// Default settings
|
||||
//
|
||||
'settings' => [// Module specific settings go here, if any
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension format="1.0">
|
||||
<extension_code>nominal_ext1</extension_code>
|
||||
<company>Combodo SARL</company>
|
||||
<author><![CDATA[Odain]]></author>
|
||||
<label><![CDATA[Ext For Test]]></label>
|
||||
<description><![CDATA[Ext For Test]]></description>
|
||||
<version>6.6.6</version>
|
||||
<modules type="array">
|
||||
<module>
|
||||
<id>nominal_ext1_module1</id>
|
||||
<version>tags/6.6.6</version>
|
||||
</module>
|
||||
</modules>
|
||||
<release_date>2023-07-19</release_date>
|
||||
<version_description><![CDATA[
|
||||
]]></version_description>
|
||||
<itop_version_min>3.2.0</itop_version_min>
|
||||
<status></status>
|
||||
<mandatory>false</mandatory>
|
||||
<more_info_url></more_info_url>
|
||||
</extension>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
|
||||
<classes>
|
||||
<class id="Feature1Module1MyClass" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>Feature1Module1MyClass</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<order>
|
||||
<columns>
|
||||
<column id="name" ascending="false"/>
|
||||
</columns>
|
||||
</order>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
</classes>
|
||||
<menus/>
|
||||
<user_rights/>
|
||||
<module_parameters/>
|
||||
</itop_design>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
//// PHP Data Model definition file
|
||||
//
|
||||
//// WARNING - WARNING - WARNING
|
||||
//// DO NOT EDIT THIS FILE (unless you know what you are doing)
|
||||
////
|
||||
//// If you use supply a datamodel.xxxx.xml file with your module
|
||||
//// the this file WILL BE overwritten by the compilation of the
|
||||
//// module (during the setup) if the datamodel.xxxx.xml file
|
||||
//// contains the definition of new classes or menus.
|
||||
////
|
||||
//// The recommended way to define new classes (for iTop 2.0) is via the XML definition.
|
||||
//// This file remains in the module's template only for the cases where there is:
|
||||
//// - either no new class or menu defined in the XML file
|
||||
//// - or no XML file at all supplied by the module
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
//
|
||||
// iTop module definition file
|
||||
//
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'nominal_ext1_module1/6.6.6',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
'label' => 'Ext For Test',
|
||||
'category' => 'business',
|
||||
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/3.2.0',
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
'installer' => '',
|
||||
|
||||
// Components
|
||||
//
|
||||
'datamodel' => [
|
||||
'model.nominal_ext1_module1.php',
|
||||
],
|
||||
'webservice' => [],
|
||||
'data.struct' => [// add your 'structure' definition XML files here,
|
||||
],
|
||||
'data.sample' => [// add your sample data XML files here,
|
||||
],
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
|
||||
// Default settings
|
||||
//
|
||||
'settings' => [// Module specific settings go here, if any
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
class iTopExtensionsMapFake extends iTopExtensionsMap
|
||||
{
|
||||
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
|
||||
{
|
||||
$this->aExtensions = [];
|
||||
$this->aExtensionsByCode = [];
|
||||
$this->aScannedDirs = [];
|
||||
}
|
||||
|
||||
public static function createFromArray($aExtensions)
|
||||
{
|
||||
$oMap = new static();
|
||||
|
||||
foreach ($aExtensions as $sCode => $aExtension) {
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->sCode = $sCode;
|
||||
$oExtension->sLabel = $sCode;
|
||||
$oExtension->bInstalled = $aExtension['installed'];
|
||||
$oExtension->aModules = $aExtension['modules'] ?? [];
|
||||
$oExtension->bCanBeUninstalled = $aExtension['uninstallable'] ?? null;
|
||||
$oExtension->sVersion = $aExtension['version'] ?? '1.0.0';
|
||||
$oExtension->aModuleInfo = [];
|
||||
$oMap->AddExtension($oExtension);
|
||||
}
|
||||
return $oMap;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,642 @@
|
||||
[
|
||||
{
|
||||
"0": "330",
|
||||
"id": "330",
|
||||
"1": "iTop",
|
||||
"name": "iTop",
|
||||
"2": "3.3.0-dev-svn",
|
||||
"version": "3.3.0-dev-svn",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"comment": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"5": "0",
|
||||
"parent_id": "0",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "331",
|
||||
"id": "331",
|
||||
"1": "authent-cas",
|
||||
"name": "authent-cas",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "332",
|
||||
"id": "332",
|
||||
"1": "authent-external",
|
||||
"name": "authent-external",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "333",
|
||||
"id": "333",
|
||||
"1": "authent-ldap",
|
||||
"name": "authent-ldap",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "334",
|
||||
"id": "334",
|
||||
"1": "authent-local",
|
||||
"name": "authent-local",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "335",
|
||||
"id": "335",
|
||||
"1": "combodo-backoffice-darkmoon-theme",
|
||||
"name": "combodo-backoffice-darkmoon-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "336",
|
||||
"id": "336",
|
||||
"1": "combodo-backoffice-fullmoon-high-contrast-theme",
|
||||
"name": "combodo-backoffice-fullmoon-high-contrast-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "337",
|
||||
"id": "337",
|
||||
"1": "combodo-backoffice-fullmoon-protanopia-deuteranopia-theme",
|
||||
"name": "combodo-backoffice-fullmoon-protanopia-deuteranopia-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "338",
|
||||
"id": "338",
|
||||
"1": "combodo-backoffice-fullmoon-tritanopia-theme",
|
||||
"name": "combodo-backoffice-fullmoon-tritanopia-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "339",
|
||||
"id": "339",
|
||||
"1": "itop-backup",
|
||||
"name": "itop-backup",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "340",
|
||||
"id": "340",
|
||||
"1": "itop-config",
|
||||
"name": "itop-config",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "341",
|
||||
"id": "341",
|
||||
"1": "itop-files-information",
|
||||
"name": "itop-files-information",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "342",
|
||||
"id": "342",
|
||||
"1": "itop-portal-base",
|
||||
"name": "itop-portal-base",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "343",
|
||||
"id": "343",
|
||||
"1": "itop-profiles-itil",
|
||||
"name": "itop-profiles-itil",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "344",
|
||||
"id": "344",
|
||||
"1": "itop-sla-computation",
|
||||
"name": "itop-sla-computation",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "345",
|
||||
"id": "345",
|
||||
"1": "itop-structure",
|
||||
"name": "itop-structure",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "346",
|
||||
"id": "346",
|
||||
"1": "itop-welcome-itil",
|
||||
"name": "itop-welcome-itil",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "347",
|
||||
"id": "347",
|
||||
"1": "itop-config-mgmt",
|
||||
"name": "itop-config-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "348",
|
||||
"id": "348",
|
||||
"1": "itop-attachments",
|
||||
"name": "itop-attachments",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "349",
|
||||
"id": "349",
|
||||
"1": "itop-tickets",
|
||||
"name": "itop-tickets",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "350",
|
||||
"id": "350",
|
||||
"1": "combodo-db-tools",
|
||||
"name": "combodo-db-tools",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.0.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.0.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "351",
|
||||
"id": "351",
|
||||
"1": "itop-core-update",
|
||||
"name": "itop-core-update",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-files-information\/2.7.0\nDepends on module: combodo-db-tools\/2.7.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-files-information\/2.7.0\nDepends on module: combodo-db-tools\/2.7.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "352",
|
||||
"id": "352",
|
||||
"1": "itop-hub-connector",
|
||||
"name": "itop-hub-connector",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "353",
|
||||
"id": "353",
|
||||
"1": "itop-oauth-client",
|
||||
"name": "itop-oauth-client",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-welcome-itil\/3.1.0,",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-welcome-itil\/3.1.0,",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "354",
|
||||
"id": "354",
|
||||
"1": "itop-themes-compat",
|
||||
"name": "itop-themes-compat",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "355",
|
||||
"id": "355",
|
||||
"1": "itop-datacenter-mgmt",
|
||||
"name": "itop-datacenter-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "356",
|
||||
"id": "356",
|
||||
"1": "itop-endusers-devices",
|
||||
"name": "itop-endusers-devices",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "357",
|
||||
"id": "357",
|
||||
"1": "itop-storage-mgmt",
|
||||
"name": "itop-storage-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "358",
|
||||
"id": "358",
|
||||
"1": "itop-virtualization-mgmt",
|
||||
"name": "itop-virtualization-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "359",
|
||||
"id": "359",
|
||||
"1": "itop-bridge-cmdb-ticket",
|
||||
"name": "itop-bridge-cmdb-ticket",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-tickets\/2.7.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-tickets\/2.7.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "360",
|
||||
"id": "360",
|
||||
"1": "itop-bridge-virtualization-storage",
|
||||
"name": "itop-bridge-virtualization-storage",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-storage-mgmt\/2.2.0\nDepends on module: itop-virtualization-mgmt\/2.2.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-storage-mgmt\/2.2.0\nDepends on module: itop-virtualization-mgmt\/2.2.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "361",
|
||||
"id": "361",
|
||||
"1": "itop-service-mgmt",
|
||||
"name": "itop-service-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.0.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.0.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "362",
|
||||
"id": "362",
|
||||
"1": "itop-bridge-cmdb-services",
|
||||
"name": "itop-bridge-cmdb-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "363",
|
||||
"id": "363",
|
||||
"1": "itop-bridge-datacenter-mgmt-services",
|
||||
"name": "itop-bridge-datacenter-mgmt-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-datacenter-mgmt\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-datacenter-mgmt\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "364",
|
||||
"id": "364",
|
||||
"1": "itop-bridge-endusers-devices-services",
|
||||
"name": "itop-bridge-endusers-devices-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-endusers-devices\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-endusers-devices\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "365",
|
||||
"id": "365",
|
||||
"1": "itop-bridge-storage-mgmt-services",
|
||||
"name": "itop-bridge-storage-mgmt-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-storage-mgmt\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-storage-mgmt\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "366",
|
||||
"id": "366",
|
||||
"1": "itop-bridge-virtualization-mgmt-services",
|
||||
"name": "itop-bridge-virtualization-mgmt-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-virtualization-mgmt\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-virtualization-mgmt\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "367",
|
||||
"id": "367",
|
||||
"1": "itop-request-mgmt",
|
||||
"name": "itop-request-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "368",
|
||||
"id": "368",
|
||||
"1": "itop-portal",
|
||||
"name": "itop-portal",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-portal-base\/2.7.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-portal-base\/2.7.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "369",
|
||||
"id": "369",
|
||||
"1": "itop-change-mgmt",
|
||||
"name": "itop-change-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0\nDepends on module: itop-tickets\/2.0.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0\nDepends on module: itop-tickets\/2.0.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"0": "330",
|
||||
"id": "330",
|
||||
"1": "iTop",
|
||||
"name": "iTop",
|
||||
"2": "3.3.0-dev-svn",
|
||||
"version": "3.3.0-dev-svn",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"comment": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"5": "0",
|
||||
"parent_id": "0",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "331",
|
||||
"id": "331",
|
||||
"1": "mandatory_module",
|
||||
"name": "mandatory_module",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "332",
|
||||
"id": "332",
|
||||
"1": "optional_module",
|
||||
"name": "optional_module",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "no"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user