diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php index 1bf761ac7c..d40cce9537 100644 --- a/setup/extensionsmap.class.inc.php +++ b/setup/extensionsmap.class.inc.php @@ -62,20 +62,22 @@ class iTopExtensionsMap * * @throws \Exception */ - private function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [], ?string $sAppRootForTests = null) + private function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [], ?string $sAppRootForTests = null, bool $bNoScan = false) { $this->aExtensions = []; $this->aExtensionsByCode = []; $this->aScannedDirs = []; - $sAppRoot = $sAppRootForTests ?? APPROOT; - $this->ScanDisk($sFromEnvironment, $sAppRoot); + if (! $bNoScan) { + $sAppRoot = $sAppRootForTests ?? APPROOT; + $this->ScanDisk($sFromEnvironment, $sAppRoot); - foreach ($aExtraDirs as $sDir) { - $this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE, bIsRootDir: true); + foreach ($aExtraDirs as $sDir) { + $this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE, bIsRootDir: true); + } + + $this->CheckDependencies($sAppRoot); } - - $this->CheckDependencies($sAppRoot); } /** @@ -261,7 +263,7 @@ class iTopExtensionsMap foreach ($aExtensionCodes as $sCode => $sLabel) { $sRealCode = is_int($sCode) ? $sLabel : $sCode; - /** @var \iTopExtension $oExtension */ + /** @var ?\iTopExtension $oExtension */ $oExtension = $this->GetFromExtensionCode($sRealCode); if (!is_null($oExtension)) { $aRemovedExtension [] = $oExtension; @@ -298,7 +300,7 @@ class iTopExtensionsMap $aSubDirectories = []; // First check if there is an extension.xml file in this directory - if (is_readable($sSearchDir.'/extension.xml')) { + if ($sParentExtensionId === null && is_readable($sSearchDir.'/extension.xml')) { $oXml = new XMLParameters($sSearchDir.'/extension.xml'); $oExtension = new iTopExtension(); $oExtension->sCode = $oXml->Get('extension_code'); @@ -357,7 +359,7 @@ class iTopExtensionsMap $oExtension->sVersion = $sModuleVersion; $oExtension->sSource = $sSource; $oExtension->bMandatory = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['mandatory']; - $oExtension->sMoreInfoUrl = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['doc.more_information']; + $oExtension->sMoreInfoUrl = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['doc.more_information'] ?? ''; $oExtension->aModules = [$sModuleName]; $oExtension->aModuleVersion[$sModuleName] = $sModuleVersion; $oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]; @@ -403,27 +405,15 @@ class iTopExtensionsMap return []; } - $aExtensions = []; - $hDir = opendir($sSearchDir); - if ($hDir !== false) { + $oiTopExtensionsMap = new iTopExtensionsMap(bNoScan: true); + $oiTopExtensionsMap->ReadDir($sSearchDir, iTopExtension::SOURCE_REMOTE, bIsRootDir: true); - // Then scan the other files and subdirectories - while (($sDir = readdir($hDir)) !== false) { - if (($sDir === '.') || ($sDir === '..') || !is_dir($sSearchDir.$sDir)) { - continue; - } - - // First check if there is an extension.xml file in this directory - if (is_readable($sSearchDir.$sDir.'/extension.xml')) { - $oXml = new XMLParameters($sSearchDir.$sDir.'/extension.xml'); - $aExtensions[$oXml->Get('extension_code')] = $oXml->Get('label'); - } - } - - closedir($hDir); + $aRes = []; + foreach ($oiTopExtensionsMap->aExtensionsByCode as $sCode => $oExtension) { + /** @var iTopExtension $oExtension */ + $aRes[$sCode] = $oExtension->sLabel; } - - return $aExtensions; + return $aRes; } /** diff --git a/setup/wizardsteps/WizStepLandingBeforeAudit.php b/setup/wizardsteps/WizStepLandingBeforeAudit.php index f4d96b3068..a60655b515 100644 --- a/setup/wizardsteps/WizStepLandingBeforeAudit.php +++ b/setup/wizardsteps/WizStepLandingBeforeAudit.php @@ -11,8 +11,8 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice public function __construct(WizardController $oWizard, $sCurrentState) { - $this->oRuntimeEnv = new RunTimeEnvironment($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV)); - $sBuildConfigFile = APPCONF.$this->oRuntimeEnv->GetBuildEnv().'/'.ITOP_CONFIG_FILE; + $this->oRuntimeEnv = new RunTimeEnvironment($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV), false); + $sBuildConfigFile = APPCONF.$this->oRuntimeEnv->GetFinalEnv().'/'.ITOP_CONFIG_FILE; $this->oConfig = new Config($sBuildConfigFile); $oWizard->SetParameter('previous_version_dir', APPROOT); @@ -36,6 +36,8 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice // should be done at the end parent::__construct($oWizard, $sCurrentState, false); + + $this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->oRuntimeEnv->GetBuildEnv()); } /** diff --git a/tests/php-static-analysis/config/base.dist.neon b/tests/php-static-analysis/config/base.dist.neon index e1ac3d09c2..e39c8bae68 100644 --- a/tests/php-static-analysis/config/base.dist.neon +++ b/tests/php-static-analysis/config/base.dist.neon @@ -10,6 +10,7 @@ parameters: bootstrapFiles: - ../../../approot.inc.php - ../../../bootstrap.inc.php + - ../../../tests/php-unit-tests/vendor/autoload.php scanFiles: # Files necessary as they contain some declarations (constants, classes, functions, ...) @@ -36,6 +37,5 @@ parameters: - ../../../env-php-unit-tests (?) # Irrelevant as it will either already be in `env-production` or might be desynchronized from `env-production` - ../../../env-toolkit (?) # Irrelevent as it will either already be in `env-production` or might be desynchronized from `env-production` (for local run only, not useful in the CI) - #- ../../../tests (?) # Exclude tests for now - - ../../../toolkit (?) # Exclude toolkit for now - - ../../../setup/compat # Exclude fake DOMDocument & DOMElement declarations + - ../../../tests (?) # Exclude tests for now + - ../../../toolkit (?) # Exlclude toolkit for now diff --git a/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php b/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php index 6db616861f..d7a5f3818f 100644 --- a/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php @@ -420,4 +420,130 @@ XML; return $aRes; } + + public function testGetExtensionsFromDir_MonoModuleExtensionWithXmlDefinition() + { + $oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(); + $sTestDir = $this->PrepareTestDir(); + + $this->CreateExtensionFolder($sTestDir, "ext1", "ext_code1", "1.5.9"); + $this->CreateModule($sTestDir, "module1", "module_code1", "9.5.1"); + self::assertEquals(['ext_code1' => 'ext1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir)); + } + + public function testGetExtensionsFromDir_MultiModuleExtension() + { + $oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(); + $sTestDir = $this->PrepareTestDir(); + + $this->CreateExtensionFolder($sTestDir, "ext1", "ext_code1", "1.5.9"); + + foreach ([1, 2, 3] as $i) { + $this->CreateModule($sTestDir, "module{$i}", "module_code{$i}", "9.5.{$i}"); + } + self::assertEquals(['ext_code1' => 'ext1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir)); + } + + public function testGetExtensionsFromDir_MultiModuleExtensionWithOneXMLDefPerModule() + { + $oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(); + $sTestDir = $this->PrepareTestDir(); + + $this->CreateExtensionFolder($sTestDir, "ext1", "ext_code1", "1.5.9"); + + foreach ([1, 2, 3] as $i) { + if (! is_dir("$sTestDir/module_code{$i}")) { + mkdir("$sTestDir/module_code{$i}"); + } + $this->CreateExtensionFolder("$sTestDir/module_code{$i}", "ext{$i}", "ext_code{$i}", "9.5.{$i}"); + $this->CreateModule($sTestDir, "module{$i}", "module_code{$i}", "9.5.{$i}"); + } + self::assertEquals(['ext_code1' => 'ext1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir)); + } + + public function testGetExtensionsFromDir_FurtherMonoModuleExtensionsWithoutXmlDefinition() + { + $oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(); + $sTestDir = $this->PrepareTestDir(); + + foreach ([1, 2, 3] as $i) { + $this->CreateModule($sTestDir, "module{$i}", "module_code{$i}", "9.5.{$i}"); + } + + $expected = [ + 'module_code1' => 'module1', + 'module_code2' => 'module2', + 'module_code3' => 'module3', + ]; + self::assertEquals($expected, $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir)); + } + + public function testGetExtensionsFromDir_MonoModuleExtensionWithoutXmlDefinition() + { + $oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(); + $sTestDir = $this->PrepareTestDir(); + + $this->CreateModule($sTestDir, "module1", "module_code1", "9.5.1"); + self::assertEquals(['module_code1' => 'module1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir)); + } + + private function PrepareTestDir(): string + { + $sTestDir = tempnam(sys_get_temp_dir(), "ext_map"); + unlink($sTestDir); + mkdir($sTestDir, 0777, true); + $this->aFileToClean[] = $sTestDir; + return $sTestDir; + } + + private function CreateExtensionFolder(string $sDir, string $sExtensionLabel, string $sExtensionCode, string $sExtensionVersion) + { + $sXmlDef = << + + $sExtensionCode + + + $sExtensionVersion + + + aaa + tags/6.6.6 + + + 2023-07-19 + 3.2.0 + + false + + +XML; + + file_put_contents($sDir.'/extension.xml', $sXmlDef); + } + + private function CreateModule(string $sDir, string $sModuleLabel, string $sModuleCode, string $sVersion) + { + $sModuleDir = $sDir."/$sModuleCode"; + if (! is_dir($sModuleDir)) { + mkdir($sModuleDir, 0777, true); + } + $sModuleContent = << '$sModuleLabel', + 'category' => 'authentication', + 'dependencies' => [], + 'mandatory' => true, + 'visible' => true, + 'datamodel' => [], + ] +); +PHP; + + file_put_contents($sModuleDir."/module.{$sModuleCode}.php", $sModuleContent); + } }