diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php index 94bee704f..31ad96680 100644 --- a/setup/extensionsmap.class.inc.php +++ b/setup/extensionsmap.class.inc.php @@ -32,6 +32,9 @@ class iTopExtensionsMap */ protected $aScannedDirs; + /** @var bool $bHasXmlInstallationFile : false when legacy 1.x package with no installation.xml */ + protected $bHasXmlInstallationFile = true; + /** * The list of all discovered extensions * @param string $sFromEnvironment The environment to scan @@ -59,6 +62,7 @@ class iTopExtensionsMap protected function ScanDisk($sEnvironment) { if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x')) { + $this->bHasXmlInstallationFile = false; //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) @@ -371,24 +375,36 @@ class iTopExtensionsMap } /** - * @param bool $bKeepMissingDependencyExtensions - * - * @return array<\iTopExtension>> + * @param bool $bKeepMissingDependencyExtensions + * @param bool $bRemoteExtensionsShouldBeMandatory + * @return array<\iTopExtension>> */ - - public function GetAllExtensionsToDisplayInSetup(bool $bKeepMissingDependencyExtensions = false, bool $bRemoteExtensionsShouldBeMandatory = true): array + public function GetAllExtensionsToDisplayInSetup(bool $bKeepExtensionsHavingMissingDependencies = false, bool $bRemoteExtensionsShouldBeMandatory = true): array { + // all extensions are loaded at first screen when no installation xml: flat display + //otherwhile wizard screen displays choice screens along extension tree (cf installation.xml) $aRes = []; foreach ($this->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) { /** @var \iTopExtension $oExtension */ - if ($oExtension->sSource !== iTopExtension::SOURCE_WIZARD && $oExtension->bVisible) { - if ($bKeepMissingDependencyExtensions || count($oExtension->aMissingDependencies) == 0) { - if (!$oExtension->bMandatory && $bRemoteExtensionsShouldBeMandatory) { - $oExtension->bMandatory = ($oExtension->sSource === iTopExtension::SOURCE_REMOTE); - } - $aRes[$oExtension->sCode] = $oExtension; - } + if (! $oExtension->bVisible) { + //skip hidden extensions + continue; } + + if ($this->bHasXmlInstallationFile && $oExtension->sSource === iTopExtension::SOURCE_WIZARD) { + //skip extensions handled in installation previous choice screens (defined in installation.xml) + continue; + } + + if (! $bKeepExtensionsHavingMissingDependencies && count($oExtension->aMissingDependencies) > 0) { + //skip extensions with dependency issues + continue; + } + + if (!$oExtension->bMandatory && $bRemoteExtensionsShouldBeMandatory) { + $oExtension->bMandatory = ($oExtension->sSource === iTopExtension::SOURCE_REMOTE); + } + $aRes[$oExtension->sCode] = $oExtension; } return $aRes; diff --git a/setup/wizardsteps/WizStepModulesChoice.php b/setup/wizardsteps/WizStepModulesChoice.php index 927a40303..89c4c96dd 100644 --- a/setup/wizardsteps/WizStepModulesChoice.php +++ b/setup/wizardsteps/WizStepModulesChoice.php @@ -649,6 +649,7 @@ EOF } } } else { + //legacy product (1.x/no installation.xml) $aOptions = $this->oExtensionsMap->GetAllExtensionsOptionInfo($bRemoteExtensionsShouldBeMandatory); // No wizard configuration provided, build a standard one with just one big list. All items are mandatory, only works when there are no conflicted modules. diff --git a/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php b/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php index df63c2ebe..3f505b6fe 100644 --- a/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php @@ -3,6 +3,7 @@ namespace Combodo\iTop\Test\UnitTest\Integration; use Combodo\iTop\Test\UnitTest\ItopTestCase; +use iTopExtension; use ItopExtensionsMap; use ModuleDiscovery; @@ -23,4 +24,131 @@ class ExtensionsMapTest extends ItopTestCase $this->assertGreaterThan(0, count($aExtensions)); } + public function testGetAllExtensionsToDisplayInSetup() + { + $oExtensionsMap = $this->GiveExtensionMapWithAllTypeOfExtensions(); + + $aExtensions = $oExtensionsMap->GetAllExtensionsToDisplayInSetup(); + $expected = [ + 'installed_ext1', + 'ext1', + ]; + $this->assertEquals($expected, array_keys($aExtensions)); + } + + public function testGetAllExtensionsToDisplayInSetup_WithExtensionsHavingDependencyIssues() + { + $oExtensionsMap = $this->GiveExtensionMapWithAllTypeOfExtensions(); + + $aExtensions = $oExtensionsMap->GetAllExtensionsToDisplayInSetup(true); + $expected = [ + 'installed_ext1', + 'installed_ext_with_deps_issues', + 'ext1', + 'ext_with_deps_issues', + ]; + $this->assertEquals($expected, array_keys($aExtensions)); + } + + public function testGetAllExtensionsToDisplayInSetup_LegacyPackage() + { + $oExtensionsMap = $this->GiveExtensionMapWithAllTypeOfExtensions(); + + $this->SetNonPublicProperty($oExtensionsMap, 'bHasXmlInstallationFile', false); + $aExtensions = $oExtensionsMap->GetAllExtensionsToDisplayInSetup(); + $expected = [ + 'installed_ext1', + 'installed_ext_in_package', + 'ext1', + 'ext_in_package', + ]; + $this->assertEquals($expected, array_keys($aExtensions)); + } + + public function testGetAllExtensionsToDisplayInSetup_LegacyPackage_WithExtensionsHavingDependencyIssues() + { + $oExtensionsMap = $this->GiveExtensionMapWithAllTypeOfExtensions(); + + $this->SetNonPublicProperty($oExtensionsMap, 'bHasXmlInstallationFile', false); + $aExtensions = $oExtensionsMap->GetAllExtensionsToDisplayInSetup(true); + $expected = [ + 'installed_ext1', + 'installed_ext_in_package', + 'installed_ext_with_deps_issues', + 'ext1', + 'ext_in_package', + 'ext_with_deps_issues', + ]; + $this->assertEquals($expected, array_keys($aExtensions)); + } + + private function GiveExtensionMapWithAllTypeOfExtensions(): iTopExtensionsMap + { + $oExtensionsMap = new iTopExtensionsMap(); + $this->SetNonPublicProperty($oExtensionsMap, 'aInstalledExtensions', []); + $this->SetNonPublicProperty($oExtensionsMap, 'aExtensions', []); + + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("installed_ext1", "123", true, iTopExtension::SOURCE_REMOTE, true, []), + 'aInstalledExtensions' + ); + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("installed_notvisible", "123", false, iTopExtension::SOURCE_REMOTE, true, []), + 'aInstalledExtensions' + ); + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("installed_ext_in_package", "123", true, iTopExtension::SOURCE_WIZARD, true, []), + 'aInstalledExtensions' + ); + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("installed_ext_with_deps_issues", "123", true, iTopExtension::SOURCE_REMOTE, true, ["aa"]), + 'aInstalledExtensions' + ); + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("ext1", "123", true, iTopExtension::SOURCE_REMOTE, true, []), + 'aExtensions' + ); + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("notvisible", "123", false, iTopExtension::SOURCE_REMOTE, true, []), + 'aExtensions' + ); + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("ext_in_package", "123", true, iTopExtension::SOURCE_WIZARD, true, []), + 'aExtensions' + ); + $this->AddExtension( + $oExtensionsMap, + $this->GivenExtension("ext_with_deps_issues", "123", true, iTopExtension::SOURCE_REMOTE, true, ["aa"]), + 'aExtensions' + ); + + return $oExtensionsMap; + } + + private function GivenExtension(string $sCode, string $sVersion, bool $bVisible, string $sSource, bool $bMandatory, array $aMissingDependencies = []): iTopExtension + { + $oExt = new iTopExtension(); + $oExt->sCode = $sCode; + $oExt->sVersion = $sVersion; + $oExt->bVisible = $bVisible; + $oExt->sSource = $sSource; + $oExt->aMissingDependencies = $aMissingDependencies; + $oExt->bMandatory = $bMandatory; + return $oExt; + } + + private function AddExtension(iTopExtensionsMap $oExtensionsMap, iTopExtension $oExt, string $mapKeyInItopExtensionMap) + { + $aMap = $this->GetNonPublicProperty($oExtensionsMap, $mapKeyInItopExtensionMap); + $aMap[$oExt->sCode.'/'.$oExt->sVersion] = $oExt; + $this->SetNonPublicProperty($oExtensionsMap, $mapKeyInItopExtensionMap, $aMap); + } + }