diff --git a/datamodels/2.x/combodo-data-feature-removal/src/Controller/DataFeatureRemovalController.php b/datamodels/2.x/combodo-data-feature-removal/src/Controller/DataFeatureRemovalController.php index a1915a9a61..a58e2377a9 100644 --- a/datamodels/2.x/combodo-data-feature-removal/src/Controller/DataFeatureRemovalController.php +++ b/datamodels/2.x/combodo-data-feature-removal/src/Controller/DataFeatureRemovalController.php @@ -145,7 +145,7 @@ class DataFeatureRemovalController extends Controller $bForceCompilation = Session::Get('bForceCompilation', false); try { - $this->Compile($aRemoveExtensionCodes, $bForceCompilation); + $this->Compile($aAddedExtensions, $aRemoveExtensionCodes, $bForceCompilation); } catch (CoreException $e) { $aParams['DataFeatureRemovalErrorMessage'] = $e->getHtmlDesc(); $this->DisplayPage($aParams, 'AnalysisResult'); @@ -162,7 +162,7 @@ class DataFeatureRemovalController extends Controller $aSelectedExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions)); $aHiddenInputs['selected_extensions'] = $this->ConvertIntoSetupFormat($aSelectedExtensions); - $oRunTimeEnvironment = $this->GetRuntimeEnvironment($aRemovedExtensions); + $oRunTimeEnvironment = $this->GetRuntimeEnvironment($aAddedExtensions, $aRemovedExtensions); $aSearchDirs = [$oRunTimeEnvironment->GetBuildDir()]; $aSelectedModules = $oRunTimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions, $aSearchDirs); $aHiddenInputs['selected_modules'] = $this->ConvertIntoSetupFormat($aSelectedModules); @@ -213,13 +213,14 @@ class DataFeatureRemovalController extends Controller } /** +* @param array $aAddedExtensions * @param array $aRemovedExtensions * @param bool $bForceCompilation * @return void * @throws \ConfigException * @throws \CoreException */ - private function Compile(array $aRemovedExtensions, bool $bForceCompilation = true): void + private function Compile(array $aAddedExtensions, array $aRemovedExtensions, bool $bForceCompilation = true): void { $sSourceEnv = MetaModel::GetEnvironment(); $sBuildDir = APPROOT."/env-$sSourceEnv-build"; @@ -234,15 +235,15 @@ class DataFeatureRemovalController extends Controller null, ['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")] ); - $this->GetRuntimeEnvironment($aRemovedExtensions)->CompileFrom($sSourceEnv); + $this->GetRuntimeEnvironment($aAddedExtensions, $aRemovedExtensions)->CompileFrom($sSourceEnv); } } - private function GetRuntimeEnvironment(array $aRemovedExtensions): RunTimeEnvironment + private function GetRuntimeEnvironment(array $aAddedExtensions, array $aRemovedExtensions): RunTimeEnvironment { if (is_null($this->oRuntimeEnvironment)) { $sSourceEnv = MetaModel::GetEnvironment(); - $this->oRuntimeEnvironment = new DryRemovalRuntimeEnvironment($sSourceEnv, $aRemovedExtensions); + $this->oRuntimeEnvironment = new DryRemovalRuntimeEnvironment($sSourceEnv, $aAddedExtensions, $aRemovedExtensions); } return $this->oRuntimeEnvironment; diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php index 9ab8410986..465a18d473 100644 --- a/setup/extensionsmap.class.inc.php +++ b/setup/extensionsmap.class.inc.php @@ -759,7 +759,7 @@ class iTopExtensionsMap public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSourceOnly = iTopExtension::SOURCE_REMOTE) { foreach ($this->GetAllExtensions() as $oExtension) { - if (($oExtension->sSource == $sInSourceOnly) && + if (/*($oExtension->sSource == $sInSourceOnly) &&*/ ($oExtension->bMarkedAsChosen == true) && (array_key_exists($sModuleNameToFind, $oExtension->aModuleVersion))) { return true; diff --git a/setup/feature_removal/DryRemovalRuntimeEnvironment.php b/setup/feature_removal/DryRemovalRuntimeEnvironment.php index 6d1835e82e..0cc4e7e19e 100644 --- a/setup/feature_removal/DryRemovalRuntimeEnvironment.php +++ b/setup/feature_removal/DryRemovalRuntimeEnvironment.php @@ -9,15 +9,17 @@ use SetupUtils; class DryRemovalRuntimeEnvironment extends RunTimeEnvironment { protected array $aExtensionsToRemoveByCode; + protected array $aExtensionCodesToAddByCode; /** * Toolset for building a run-time environment * * @param string $sSourceEnv: environment from which setup is inspired to simulate extension removal and usee CompileFrom... */ - public function __construct($sSourceEnv = ITOP_DEFAULT_ENV, array $aExtensionCodesToRemove = []) + public function __construct($sSourceEnv = ITOP_DEFAULT_ENV, array $aExtensionCodesToAdd = [], array $aExtensionCodesToRemove = []) { parent::__construct($sSourceEnv, false); + $this->aExtensionCodesToAddByCode = $aExtensionCodesToAdd; $this->aExtensionsToRemoveByCode = $aExtensionCodesToRemove; $this->Prepare($sSourceEnv, $this->sBuildEnv); } @@ -34,13 +36,14 @@ class DryRemovalRuntimeEnvironment extends RunTimeEnvironment SetupUtils::copydir(APPROOT."/data/$sSourceEnv-modules", APPROOT."/data/$sBuildEnv-modules"); SetupUtils::copydir(APPROOT."/conf/$sSourceEnv", APPROOT."/conf/$sBuildEnv"); - $this->DeclareExtensionAsRemoved($this->aExtensionsToRemoveByCode); - } + $this->InitExtensionMap($sSourceEnv, $this->GetSourceDir($this->sBuildEnv)); + $this->GetExtensionMap()->DeclareExtensionAsRemoved($this->aExtensionsToRemoveByCode); - private function DeclareExtensionAsRemoved(array $aExtensionCodes): void - { - $oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv); - $oExtensionsMap->DeclareExtensionAsRemoved($aExtensionCodes); + foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) { + if (in_array($oExtension->sCode, $this->aExtensionCodesToAddByCode)) { + $oExtension->bMarkedAsChosen = true; + } + } } public function Cleanup(): void diff --git a/setup/feature_removal/SetupAudit.php b/setup/feature_removal/SetupAudit.php index 35c4628d9b..9e842b4a45 100644 --- a/setup/feature_removal/SetupAudit.php +++ b/setup/feature_removal/SetupAudit.php @@ -17,6 +17,11 @@ class SetupAudit extends AbstractSetupAudit $this->sEnvAfter = $sEnvAfter ?? "$sEnvBefore-build"; } + public function GetEnvAfter(): string + { + return $this->sEnvAfter; + } + public function ComputeClasses(): void { if ($this->bClassesInitialized) { diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index f7331c610e..76448057bb 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -71,15 +71,63 @@ class RunTimeEnvironment return $this->oExtensionsMap; } - public function InitExtensionMap($aExtraDirs, $oSourceConfig) + private array $aExtraDirs; + private ?array $aDirsToCompile = null; + private ?Config $oSourceConfig = null; + + protected function GetDirsToCompile(string $sSourceDir, string $sSourceEnv): array { - // Actually read the modules available for the build environment, - // but get the selection from the source environment and finally - // mark as (automatically) chosen all the "remote" modules present in the - // build environment (data/-modules) - // The actual choices will be recorded by RecordInstallation below - $this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv, $aExtraDirs); - $this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig); + if (is_null($this->aDirsToCompile)) { + + $sSourceDirFull = APPROOT.$sSourceDir; + if (!is_dir($sSourceDirFull)) { + throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)"); + } + $aDirsToCompile = [$sSourceDirFull]; + if (is_dir(APPROOT.'extensions')) { + $aDirsToCompile[] = APPROOT.'extensions'; + } + $sExtraDir = utils::GetDataPath().$this->sBuildEnv.'-modules/'; + if (is_dir($sExtraDir)) { + $aDirsToCompile[] = $sExtraDir; + } + + $this->aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile); + $this->aDirsToCompile = array_merge($aDirsToCompile, $this->aExtraDirs); + } + + return $this->aDirsToCompile; + } + + protected function GetSourceDir(string $sSourceEnv): mixed + { + return $this->GetSourceConfig($sSourceEnv)->Get('source_dir'); + } + + protected function GetSourceConfig(string $sSourceEnv): Config + { + if (is_null($this->oSourceConfig)) { + $this->oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv)); + } + + return $this->oSourceConfig; + } + + public function InitExtensionMap($sSourceEnv, $sSourceDir) + { + //required to init $this->aExtraDirs + $this->GetDirsToCompile($sSourceDir, $sSourceEnv); + $oSourceConfig = $this->GetSourceConfig($sSourceEnv); + + if (is_null($this->oExtensionsMap)) { + // Actually read the modules available for the build environment, + // but get the selection from the source environment and finally + // mark as (automatically) chosen all the "remote" modules present in the + // build environment (data/-modules) + // The actual choices will be recorded by RecordInstallation below + $this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv, $this->aExtraDirs); + $this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig); + } } /** @@ -444,33 +492,12 @@ class RunTimeEnvironment /** * Get the installed modules (only the installed ones) + * @return \MFModule[] */ - protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) + protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir): array { - $sSourceDirFull = APPROOT.$sSourceDir; - if (!is_dir($sSourceDirFull)) { - throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)"); - } - $aDirsToCompile = [$sSourceDirFull]; - if (is_dir(APPROOT.'extensions')) { - $aDirsToCompile[] = APPROOT.'extensions'; - } - $sExtraDir = utils::GetDataPath().$this->sBuildEnv.'-modules/'; - if (is_dir($sExtraDir)) { - $aDirsToCompile[] = $sExtraDir; - } - - $aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile); - $aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs); - - $oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE); - - // Actually read the modules available for the build environment, - // but get the selection from the source environment and finally - // mark as (automatically) chosen all the "remote" modules present in the - // build environment (data/-modules) - // The actual choices will be recorded by RecordInstallation below - $this->InitExtensionMap($aExtraDirs, $oSourceConfig); + $this->InitExtensionMap($sSourceEnv, $sSourceDir); + $oSourceConfig = $this->GetSourceConfig($sSourceEnv); $this->GetExtensionMap()->LoadChoicesFromDatabase($oSourceConfig); foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) { if ($this->IsExtensionSelected($oExtension)) { @@ -478,11 +505,11 @@ class RunTimeEnvironment } } + $aDirsToCompile = $this->GetExtraDirsToCompile($sSourceDir); $aModulesToLoad = $this->GetModulesToLoad($this->sFinalEnv, $aDirsToCompile); $aAvailableModules = $this->AnalyzeInstallation($oSourceConfig, $aDirsToCompile, true, $aModulesToLoad); // Do load the required modules - // $oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries'); $aRet = []; @@ -1378,9 +1405,7 @@ class RunTimeEnvironment */ public function CompileFrom($sSourceEnv, $bUseSymLinks = null) { - $oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv)); - $sSourceDir = $oSourceConfig->Get('source_dir'); - + $sSourceDir = $this->GetSourceDir($sSourceEnv); $sSourceDirFull = APPROOT.$sSourceDir; // Do load the required modules // @@ -1700,21 +1725,29 @@ class RunTimeEnvironment } $aExtensionDirs = []; + $aFromSelectedExtensionModules=[]; foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) { if ($oExtension->bMarkedAsChosen && is_dir($oExtension->sSourceDir)) { $aExtensionDirs [] = $oExtension->sSourceDir; + $aFromSelectedExtensionModules=array_merge($aFromSelectedExtensionModules, $oExtension->aModules); } } SetupLog::Info(__METHOD__, null, ['ext_dirs' => $aExtensionDirs]); $aModuleIdsToLoad = InstallationChoicesToModuleConverter::GetInstance()->GetModules($aChoices, $aSearchDirs, $sInstallFilePath, $aExtensionDirs); $aModulesToLoad = []; + foreach ($aModuleIdsToLoad as $sModuleId) { $oModule = new Module($sModuleId); $sModuleName = $oModule->GetModuleName(); $aModulesToLoad[] = $sModuleName; } + foreach ($aFromSelectedExtensionModules as $sModuleName){ + if (! in_array($sModuleName, $aModulesToLoad)){ + $aModulesToLoad[] = $sModuleName; + } + } return $aModulesToLoad; } diff --git a/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php b/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php index 4cd3b9d900..72d6c2cbf1 100644 --- a/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php +++ b/tests/php-unit-tests/src/Service/UnitTestRunTimeEnvironment.php @@ -108,7 +108,7 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment /** * @inheritDoc */ - protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) + protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir): array { \SetupLog::Info(__METHOD__); $aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir); diff --git a/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php b/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php index c9efefa4bf..fc0668673a 100644 --- a/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php @@ -9,11 +9,14 @@ use Combodo\iTop\Setup\FeatureRemoval\SetupAudit; use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase; use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment; use MetaModel; +use SetupUtils; class SetupAuditTest extends ItopCustomDatamodelTestCase { public const ENVT = 'php-unit-extensionremoval-tests'; + private ?string $sDirPathToRemoveFromExtensionFolder = null; + public function GetDatamodelDeltaAbsPath(): string { //no delta: empty path provided @@ -44,6 +47,14 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase $this->RequireOnceItopFile('/setup/feature_removal/DryRemovalRuntimeEnvironment.php'); } + protected function tearDown(): void + { + parent::tearDown(); + if (!is_null($this->sDirPathToRemoveFromExtensionFolder) && is_dir($this->sDirPathToRemoveFromExtensionFolder)) { + SetupUtils::rrmdir($this->sDirPathToRemoveFromExtensionFolder); + } + } + public function GetTestEnvironment(): string { return self::ENVT; @@ -51,7 +62,11 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase public function testComputeDryRemoval() { - $oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment($this->GetTestEnvironment(), ['nominal_ext1', 'finalclass_ext2']); + $this->sDirPathToRemoveFromExtensionFolder = APPROOT.'/extensions/finalclass_ext3'; + SetupUtils::copydir(__DIR__.'/other_features/finalclass_ext3', $this->sDirPathToRemoveFromExtensionFolder); + clearstatcache(); + + $oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment($this->GetTestEnvironment(), ['finalclass_ext3'], ['nominal_ext1', 'finalclass_ext2']); $oDryRemovalRuntimeEnvt->CompileFrom($this->GetTestEnvironment()); $oSetupAudit = new SetupAudit($this->GetTestEnvironment()); @@ -67,6 +82,15 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase "FinalClassFeature2Module1MyFinalClassFromLocation" => 0, ]; $this->assertEqualsCanonicalizing($expected, $oSetupAudit->RunDataAudit()); + + $aClassesAfter = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($oSetupAudit->GetEnvAfter()); + $expected = [ + "FinalClassFeature3Module1MyClass", + "FinalClassFeature3Module1MyFinalClassFromLocation", + ]; + foreach ($expected as $sAddedClass) { + $this->assertContains($sAddedClass, $aClassesAfter, "After DryRemoval compilation DM should contain classes coming from finalclass_ext3 extension"); + } } public function testGetRemovedClassesFromSetupWizard() diff --git a/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/extension.xml b/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/extension.xml new file mode 100644 index 0000000000..c4b0e7f720 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/extension.xml @@ -0,0 +1,22 @@ + + + finalclass_ext3 + Combodo SARL + + + + 6.6.6 + + + finalclass_ext3_module1 + tags/6.6.6 + + + 2023-07-19 + + 3.2.0 + + false + + \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/finalclass_ext3_module1/datamodel.finalclass_ext3_module1.xml b/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/finalclass_ext3_module1/datamodel.finalclass_ext3_module1.xml new file mode 100644 index 0000000000..84049f9809 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/finalclass_ext3_module1/datamodel.finalclass_ext3_module1.xml @@ -0,0 +1,76 @@ + + + + + + + bizmodel,searchable + false + FinalClassFeature3Module1MyFinalClassFromLocation + + + + + + + + + + + + + + + + + + + + + name2 + + false + + + + + Location + + + + bizmodel,searchable + false + FinalClassFeature3Module1MyClass + + + + + + + + + + + + + + + + + + + name + + false + + + + + + cmdbAbstractObject + + + + + + \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/finalclass_ext3_module1/model.finalclass_ext3_module1.php b/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/finalclass_ext3_module1/model.finalclass_ext3_module1.php new file mode 100644 index 0000000000..acf219fa12 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/feature_removal/other_features/finalclass_ext3/finalclass_ext3_module1/model.finalclass_ext3_module1.php @@ -0,0 +1,16 @@ + 'Ext For Test', + 'category' => 'business', + + // Setup + // + 'dependencies' => [ + 'itop-structure/3.2.0', + ], + 'mandatory' => false, + 'visible' => true, + 'installer' => '', + + // Components + // + 'datamodel' => [ + 'model.finalclass_ext3_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 + ], + ] +);