review: ModuleDiscovery:GetModulesOrderedByDependencies replacing deprecated GetAvailableModules method

This commit is contained in:
odain
2026-02-03 06:02:11 +01:00
parent d6f37b5197
commit e35e7859ba
7 changed files with 21 additions and 223 deletions

View File

@@ -2766,7 +2766,7 @@ class Config
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
$aAddOns = $oEmptyConfig->GetAddOns();
$aModules = ModuleDiscovery::GetAvailableModules([APPROOT.$sModulesDir]);
$aModules = ModuleDiscovery::GetModulesOrderedByDependencies([APPROOT.$sModulesDir]);
foreach ($aModules as $sModuleId => $aModuleInfo) {
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules)) {

View File

@@ -329,7 +329,7 @@ class iTopExtensionsMap
$aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs);
try {
ModuleDiscovery::GetAvailableModules($aSearchDirs, true);
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true);
} catch (MissingDependencyException $e) {
// Some modules have missing dependencies
// Let's check what is the impact at the "extensions" level

View File

@@ -1801,7 +1801,7 @@ EOF
*/
public function FindModules()
{
$aAvailableModules = ModuleDiscovery::GetAvailableModules($this->aRootDirs);
$aAvailableModules = ModuleDiscovery::GetModulesOrderedByDependencies($this->aRootDirs);
$aResult = [];
foreach ($aAvailableModules as $sId => $aModule) {
$oModule = new MFModule($sId, $aModule['root_dir'], $aModule['label'], isset($aModule['auto_select']));

View File

@@ -196,21 +196,6 @@ class ModuleDiscovery
}
}
/**
* Get the list of "discovered" modules, ordered based on their (inter) dependencies
*
* @param bool $bAbortOnMissingDependency ...
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
*
* @return array
* @throws \MissingDependencyException
*/
protected static function GetModules($bAbortOnMissingDependency = false, $aModulesToLoad = null)
{
// Order the modules to take into account their inter-dependencies
return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad);
}
/**
* Arrange an list of modules, based on their (inter) dependencies
* @param array $aModules The list of modules to process: 'id' => $aModuleInfo
@@ -254,7 +239,7 @@ class ModuleDiscovery
self::$m_aRemovedExtensions = $aRemovedExtension;
}
public static function Init($aSearchDirs): void
private static function Init($aSearchDirs): void
{
if (self::$m_aSearchDirs != $aSearchDirs) {
self::ResetCache();
@@ -277,10 +262,7 @@ class ModuleDiscovery
}
/**
* Search (on the disk) for all defined iTop modules, load them and returns the list (as an array)
* of the possible iTop modules to install
* Compute dependency as well (compatibilities & order)
*
* Return all modules found on disk ordered by dependencies. Skipping modules coming from extensions declared as removed (@see ModuleDiscovery::DeclareRemovedExtensions)
* @param $aSearchDirs array of directories to search (absolute paths)
* @param bool $bAbortOnMissingDependency ...
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
@@ -288,17 +270,23 @@ class ModuleDiscovery
* @return array A big array moduleID => ModuleData
* @throws \Exception
*/
public static function GetAvailableModules($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
public static function GetModulesOrderedByDependencies($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
{
self::Init($aSearchDirs);
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad);
}
/**
* Search (on the disk) for all defined iTop modules, load them and returns the list (as an array)
* of the possible iTop modules to install
* No dependency consideration here.
* @deprecated use \ModuleDiscovery::GetModulesOrderedByDependencies instead
*/
public static function GetAvailableModules($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
{
return ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, $bAbortOnMissingDependency, $aModulesToLoad);
}
/**
* Return all modules found on disk (without any dependency consideration). Skipping modules coming from extensions declared as removed (@see ModuleDiscovery::DeclareRemovedExtensions)
*
* @param $aSearchDirs array of directories to search (absolute paths)
*
@@ -309,7 +297,7 @@ class ModuleDiscovery
{
self::Init($aSearchDirs);
$aModulesNonRemoved = [];
$aNonRemovedModules = [];
foreach (self::$m_aModules as $sModuleId => $aModuleInfo) {
$oModule = new Module($sModuleId);
$sModuleName = $oModule->GetModuleName();
@@ -318,10 +306,10 @@ class ModuleDiscovery
continue;
}
$aModulesNonRemoved[$sModuleId] = $aModuleInfo;
$aNonRemovedModules[$sModuleId] = $aModuleInfo;
}
return $aModulesNonRemoved;
return $aNonRemovedModules;
}
public static function ResetCache()

View File

@@ -73,7 +73,7 @@ class AnalyzeInstallation
//test only
$aAvailableModules = $this->aAvailableModules;
} else {
$aAvailableModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
$aAvailableModules = ModuleDiscovery::GetModulesOrderedByDependencies($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
}
foreach ($aAvailableModules as $sModuleId => $aModuleInfo) {

View File

@@ -1,190 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Setup\ModuleDependency\DependencyExpression;
require_once __DIR__.'/ModuleInstallationException.php';
require_once(APPROOT.'/setup/moduledependency/module.class.inc.php');
class InstallationChoicesService
{
private static ?InstallationChoicesService $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): InstallationChoicesService
{
if (!isset(self::$oInstance)) {
self::$oInstance = new InstallationChoicesService();
}
return self::$oInstance;
}
final public static function SetInstance(?InstallationChoicesService $oInstance): void
{
self::$oInstance = $oInstance;
}
/**
* @param array $aInstallationChoices
* @param array $aSearchDirs
*
* @return array
* @throws \ModuleInstallationException
*/
public function GetInstalledModules(array $aInstallationChoices, array $aSearchDirs, ?string $sInstallationFilePath = null): array
{
$aPackageModules = ModuleDiscovery::GetAllModules($aSearchDirs);
$bInstallationFileProvided = ! is_null($sInstallationFilePath) && is_file($sInstallationFilePath);
if ($bInstallationFileProvided) {
$oXMLParameters = new XMLParameters($sInstallationFilePath);
$aSteps = $oXMLParameters->Get('steps', []);
if (!is_array($aSteps)) {
return [];
}
} else {
$aSteps = $aInstallationChoices;
}
$aInstalledModuleNames = $this->FindInstalledPackageModules($aPackageModules, $aInstallationChoices, $aSteps, $bInstallationFileProvided);
$aInstalledModules = [];
foreach (array_keys($aPackageModules) as $sModuleId) {
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
if (in_array($sModuleName, $aInstalledModuleNames)) {
$aInstalledModules[] = $sModuleId;
}
}
return $aInstalledModules;
}
private function FindInstalledPackageModules(array $aPackageModules, array $aInstallationChoices, array $aInstallationDescription, bool $bInstallationFileProvided): array
{
$aInstalledModules = [];
$this->ProcessDefaultModules($aPackageModules, $aInstalledModules);
if ($bInstallationFileProvided) {
$this->ProcessInstallationChoices(array_keys($aInstallationChoices), $aInstallationDescription, $aInstalledModules);
} else {
$aInstalledModules = array_merge($aInstalledModules, $aInstallationChoices);
}
$this->ProcessAutoSelectModules($aPackageModules, $aInstalledModules);
return array_keys($aInstalledModules);
}
private function ProcessDefaultModules(array $aPackageModules, array &$aInstalledModules): void
{
foreach ($aPackageModules as $sModuleId => $aModule) {
if (($sModuleId != ROOT_MODULE)) {
if (isset($aModule['auto_select'])) {
continue;
}
if (($aModule['category'] == 'authentication') || (!$aModule['visible'])) {
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
$aInstalledModules[$sModuleName] = true;
}
}
}
}
private function ProcessAutoSelectModules(array $aPackageModules, array &$aInstalledModules): void
{
foreach ($aPackageModules as $sModuleId => $aModule) {
if (($sModuleId !== ROOT_MODULE) && isset($aModule['auto_select'])) {
try {
SetupInfo::SetSelectedModules($aInstalledModules);
$bSelected = DependencyExpression::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($aModule['auto_select']);
if ($bSelected) {
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
$aInstalledModules[$sModuleName] = true;
}
} catch (Exception $e) {
IssueLog::Error('Error evaluating module auto-select', null, [
'module' => $sModuleId,
'error' => $e->getMessage(),
'evaluated code' => $aModule['auto_select'],
'stacktrace' => $e->getTraceAsString(),
]);
}
}
}
}
private function ProcessInstallationChoices(array $aInstallationChoices, array $aInstallationDescription, array &$aInstalledModules): void
{
foreach ($aInstallationDescription as $aStepInfo) {
$aOptions = $aStepInfo['options'] ?? null;
if (is_array($aOptions)) {
foreach ($aOptions as $aChoiceInfo) {
$this->ProcessSelectedChoice($aInstallationChoices, $aChoiceInfo, $aInstalledModules);
}
}
$aOptions = $aStepInfo['alternatives'] ?? null;
if (is_array($aOptions)) {
foreach ($aOptions as $aChoiceInfo) {
$this->ProcessSelectedChoice($aInstallationChoices, $aChoiceInfo, $aInstalledModules);
}
}
}
}
private function ProcessSelectedChoice(array $aInstallationChoices, array $aChoiceInfo, array &$aInstalledModules)
{
if (!is_array($aChoiceInfo)) {
return;
}
$sMandatory = $aChoiceInfo['mandatory'] ?? 'false';
$aCurrentModules = $aChoiceInfo['modules'] ?? [];
$sExtensionCode = $aChoiceInfo['extension_code'];
$bSelected = ($sMandatory === 'true') || in_array($sExtensionCode, $aInstallationChoices);
if (!$bSelected) {
return;
}
foreach ($aCurrentModules as $sModuleId) {
$aInstalledModules[$sModuleId] = true;
}
$aAlternatives = $aChoiceInfo['alternatives'] ?? null;
if (is_array($aAlternatives)) {
foreach ($aAlternatives as $aSubChoiceInfo) {
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
}
}
$aSubOptionsChoiceInfo = $aChoiceInfo['sub_options'] ?? null;
if (is_array($aSubOptionsChoiceInfo)) {
$aSubOptions = $aSubOptionsChoiceInfo['options'] ?? null;
if (is_array($aSubOptions)) {
foreach ($aSubOptions as $aSubChoiceInfo) {
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
}
}
$aSubAlternatives = $aSubOptionsChoiceInfo['alternatives'] ?? null;
if (is_array($aSubAlternatives)) {
foreach ($aSubAlternatives as $aSubChoiceInfo) {
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
}
}
}
}
}

View File

@@ -509,7 +509,7 @@ class SetupUtils
}
require_once(APPROOT.'setup/modulediscovery.class.inc.php');
try {
ModuleDiscovery::GetAvailableModules($aDirsToScan, true, $aSelectedModules);
ModuleDiscovery::GetModulesOrderedByDependencies($aDirsToScan, true, $aSelectedModules);
} catch (Exception $e) {
$aResult[] = new CheckResult(CheckResult::ERROR, $e->getMessage());
}