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 df0cbecf49..9c2b098d03 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 @@ -344,7 +344,7 @@ class DataFeatureRemovalController extends Controller 'uninstallable' => $oExtension->CanBeUninstalled(), 'remote' => $oExtension->IsRemote(), 'missing' => $oExtension->bRemovedFromDisk, - 'cannot-be-installed' => count($oExtension->aMissingDependencies) > 0, + 'cannot-be-installed' => $oExtension->HasDependencyIssue(), ], ]; diff --git a/datamodels/2.x/itop-hub-connector/land.php b/datamodels/2.x/itop-hub-connector/land.php index ed3cd0d0d1..f0921e33f4 100644 --- a/datamodels/2.x/itop-hub-connector/land.php +++ b/datamodels/2.x/itop-hub-connector/land.php @@ -159,7 +159,7 @@ function DoInstall(WebPage $oPage) foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) { if ($oExtension->sSource == iTopExtension::SOURCE_REMOTE) { - if (count($oExtension->aMissingDependencies) > 0) { + if ($oExtension->HasDependencyIssue()) { $oPage->add('
'); $oPage->add(' '); $sTitle = Dict::Format('iTopHub:InstallationEffect:MissingDependencies_Details', implode(', ', $oExtension->aMissingDependencies)); diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php index 0f378d48f7..bee2fcce5b 100644 --- a/setup/extensionsmap.class.inc.php +++ b/setup/extensionsmap.class.inc.php @@ -545,7 +545,7 @@ class iTopExtensionsMap continue; } - if (! $bKeepExtensionsHavingMissingDependencies && count($oExtension->aMissingDependencies) > 0) { + if (! $bKeepExtensionsHavingMissingDependencies && $oExtension->HasDependencyIssue()) { //skip extensions with dependency issues continue; } @@ -562,19 +562,20 @@ class iTopExtensionsMap public function GetAllExtensionsOptionInfo(bool $bRemoteExtensionsShouldBeMandatory = true): array { $aRes = []; - foreach ($this->GetAllExtensionsToDisplayInSetup(false, $bRemoteExtensionsShouldBeMandatory) as $sCode => $oExtension) { + foreach ($this->GetAllExtensionsToDisplayInSetup(true, $bRemoteExtensionsShouldBeMandatory) as $sCode => $oExtension) { $aRes[] = [ - 'extension_code' => $oExtension->sCode, - 'title' => $oExtension->sLabel, - 'description' => $oExtension->sDescription, - 'more_info' => $oExtension->sMoreInfoUrl, - 'default' => true, // by default offer to install all modules - 'modules' => $oExtension->aModules, - 'mandatory' => $oExtension->bMandatory, - 'source_label' => $oExtension->GetExtensionSourceLabel(), - 'uninstallable' => $oExtension->CanBeUninstalled(), - 'missing' => $oExtension->bRemovedFromDisk, - 'version' => $oExtension->sVersion, + 'extension_code' => $oExtension->sCode, + 'title' => $oExtension->sLabel, + 'description' => $oExtension->sDescription, + 'more_info' => $oExtension->sMoreInfoUrl, + 'default' => true, // by default offer to install all modules + 'modules' => $oExtension->aModules, + 'mandatory' => $oExtension->bMandatory, + 'dependency_issue' => $oExtension->HasDependencyIssue(), + 'source_label' => $oExtension->GetExtensionSourceLabel(), + 'uninstallable' => $oExtension->CanBeUninstalled(), + 'missing' => $oExtension->bRemovedFromDisk, + 'version' => $oExtension->sVersion, ]; } diff --git a/setup/itopextension.class.inc.php b/setup/itopextension.class.inc.php index a84b69deb4..0e009680b6 100644 --- a/setup/itopextension.class.inc.php +++ b/setup/itopextension.class.inc.php @@ -200,4 +200,9 @@ class iTopExtension { return $this->sLabel !== ''; } + + public function HasDependencyIssue(): bool + { + return count($this->aMissingDependencies) > 0; + } } diff --git a/setup/unattended-install/InstallationFileService.php b/setup/unattended-install/InstallationFileService.php index 33de7dac52..8bf5660cbd 100644 --- a/setup/unattended-install/InstallationFileService.php +++ b/setup/unattended-install/InstallationFileService.php @@ -338,7 +338,7 @@ class InstallationFileService continue; } if ($this->CanChooseUnpackageExtension($oExtension)) { - if (($oExtension->bVisible) && (count($oExtension->aMissingDependencies) === 0)) { + if ($oExtension->bVisible && !$oExtension->HasDependencyIssue()) { $aCurrentModules = []; $aUnselectableModules = []; $bIsExtensionSelectable = true; diff --git a/setup/wizardsteps/WizStepModulesChoice.php b/setup/wizardsteps/WizStepModulesChoice.php index 2d62c15707..7494d92b75 100644 --- a/setup/wizardsteps/WizStepModulesChoice.php +++ b/setup/wizardsteps/WizStepModulesChoice.php @@ -587,7 +587,7 @@ EOF break; } } - if ((isset($aChoice['mandatory']) && $aChoice['mandatory']) || + if ((isset($aChoice['mandatory']) && $aChoice['mandatory'] && (!isset($aChoice['dependency_issue']) || !$aChoice['dependency_issue'])) || (isset($aSelectedChoices[$sChoiceId]) && ($aSelectedChoices[$sChoiceId] == $sChoiceId))) { $sDisplayChoices .= '
  • '.$aChoice['title'].'
  • '; if (isset($aChoice['modules'])) { @@ -637,7 +637,7 @@ EOF if ($sChoiceName == null) { $sChoiceName = $sChoiceId; } - if ((isset($aChoice['mandatory']) && $aChoice['mandatory']) || + if ((isset($aChoice['mandatory']) && $aChoice['mandatory'] && (!isset($aChoice['dependency_issue']) || !$aChoice['dependency_issue'])) || (isset($aSelectedChoices[$sChoiceName]) && ($aSelectedChoices[$sChoiceName] == $sChoiceId))) { $sDisplayChoices .= '
  • '.$aChoice['title'].'
  • '; if ($aSelectedExtensions !== null) { @@ -751,12 +751,16 @@ EOF $bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true; $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']); $bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled; + $bDependencyIssue = $oITopExtension->HasDependencyIssue(); $bChecked = $bSelected; $bDisabled = false; if ($bMissingFromDisk) { $bDisabled = true; $bChecked = false; + } elseif ($bDependencyIssue) { + $bDisabled = true; + $bChecked = false; } elseif ($bMandatory) { $bDisabled = true; $bChecked = true; @@ -787,6 +791,8 @@ EOF return [ 'uninstallable' => $bCanBeUninstalled, + 'dependency_issue' => $bDependencyIssue, + 'mandatory' => $bMandatory, 'missing' => $bMissingFromDisk, 'installed' => $bInstalled, 'disabled' => $bDisabled, @@ -805,7 +811,7 @@ EOF $sChoiceId = $sParentId.self::$SEP.$index; $aFlags = $this->ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade); - if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) { + if ($aFlags['disabled'] && !$aFlags['checked'] && !$bDisableUninstallCheck && (!$aFlags['uninstallable'] || $aFlags['mandatory'])) { $this->bCanMoveForward = false;//Disable "Next" } @@ -882,6 +888,9 @@ EOF if (!$aFlags['uninstallable']) { $sTooltip .= '
    cannot be uninstalled
    '; } + if ($aFlags['dependency_issue']) { + $sTooltip .= '
    cannot be installed
    '; + } $sMetadata = ''; if (isset($aChoice['version']) && isset($aChoice['source_label'])) { @@ -939,10 +948,6 @@ EOF public function GetNextButtonLabel() { - if (!$this->bCanMoveForward) { - return 'Non-uninstallable extension missing'; - } - if ($this->GetStepInfo(1 + $this->GetStepIndex()) === null) { return 'Check compatibility'; } diff --git a/sources/Application/UI/Base/Layout/Extension/ExtensionDetailsUIBlockFactory.php b/sources/Application/UI/Base/Layout/Extension/ExtensionDetailsUIBlockFactory.php index 1bbe6278c1..c77163f490 100644 --- a/sources/Application/UI/Base/Layout/Extension/ExtensionDetailsUIBlockFactory.php +++ b/sources/Application/UI/Base/Layout/Extension/ExtensionDetailsUIBlockFactory.php @@ -26,11 +26,11 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory $aBadges = []; $bUninstallable = $aExtraFlags['uninstallable'] ?? true; $bMissingFromDisk = $aExtraFlags['missing'] ?? false; - $bCannotBeInstalled = $aExtraFlags['cannot-be-installed'] ?? false; + $bDependencyIssue = $aExtraFlags['dependency_issue'] ?? false; $bSelected = $aExtraFlags['selected'] ?? true; $bDisabled = $aExtraFlags['disabled'] ?? false; $bRemote = $aExtraFlags['remote'] ?? false; - self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk, $bCannotBeInstalled, $sCode); + self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk, $bDependencyIssue, $sCode); $oBadgeInstalled = BadgeUIBlockFactory::MakeGreen( Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled'), Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled+'), @@ -48,7 +48,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory $oExtensionDetails = new ExtensionDetails($sCode, $sLabel, $sDescription, $aMetaData, $aBadges, $sAbout); $oExtensionDetails->GetToggler()->SetIsToggled(true); - if ($bMissingFromDisk || $bCannotBeInstalled) { + if ($bMissingFromDisk || $bDependencyIssue) { $oExtensionDetails->GetToggler()->SetIsToggled(false); $oExtensionDetails->GetToggler()->SetIsDisabled(true); } elseif ((!$bUninstallable || $bRemote) && !$bDisabled) { @@ -71,10 +71,10 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory { $aBadges = []; $bUninstallable = $aExtraFlags['uninstallable'] ?? true; - $bCannotBeInstalled = $aExtraFlags['cannot-be-installed'] ?? false; + $bDependencyIssue = $aExtraFlags['dependency_issue'] ?? false; $bSelected = $aExtraFlags['selected'] ?? false; $bDisabled = $aExtraFlags['disabled'] ?? false; - self::AddExtraBadges($aBadges, $bUninstallable, false, $bCannotBeInstalled, $sCode); + self::AddExtraBadges($aBadges, $bUninstallable, false, $bDependencyIssue, $sCode); $oBadgeInstalled = BadgeUIBlockFactory::MakeGrey( Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled'), Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled+'), @@ -91,7 +91,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory $aBadges[] = $oBadgeToBeUninstalled; $oExtensionDetails = new ExtensionDetails($sCode, $sLabel, $sDescription, $aMetaData, $aBadges, $sAbout); - if ($bCannotBeInstalled) { + if ($bDependencyIssue) { $oExtensionDetails->GetToggler()->SetIsToggled(false); $oExtensionDetails->GetToggler()->SetIsDisabled(true); } @@ -107,7 +107,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory return $oExtensionDetails; } - private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk, bool $bCannotBeInstalled, string $sCode) + private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk, bool $bDependencyIssue, string $sCode) { if (!$bUninstallable) { $aBadges[] = BadgeUIBlockFactory::MakeYellow( @@ -123,7 +123,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory self::GetBadgeId($sCode, self::BADGE_ID_MISSING_FROM_DISK) ); } - if ($bCannotBeInstalled) { + if ($bDependencyIssue) { $aBadges[] = BadgeUIBlockFactory::MakeOrange( Dict::S('UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled'), Dict::S('UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+'), diff --git a/tests/php-unit-tests/unitary-tests/setup/WizStepModulesChoiceTest.php b/tests/php-unit-tests/unitary-tests/setup/WizStepModulesChoiceTest.php index 717ee8bd85..2f538a0fd9 100644 --- a/tests/php-unit-tests/unitary-tests/setup/WizStepModulesChoiceTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/WizStepModulesChoiceTest.php @@ -49,6 +49,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => false, 'disabled' => false, 'checked' => false, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'A selected but not installed extension should be checked and enabled' => [ @@ -70,6 +72,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => false, 'disabled' => false, 'checked' => true, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'A missing extension should be disabled and unchecked' => [ @@ -89,6 +93,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => true, 'checked' => false, + 'dependency_issue' => true, + 'mandatory' => false, ], ], 'A missing extension should always be disabled and unchecked, even when mandatory' => [ @@ -108,6 +114,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => true, 'checked' => false, + 'dependency_issue' => true, + 'mandatory' => true, ], ], 'A missing extension should always be disabled and unchecked, even when non-uninstallable' => [ @@ -127,6 +135,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => true, 'checked' => false, + 'dependency_issue' => true, + 'mandatory' => true, ], ], 'An installed but not selected extension should not be checked and be enabled' => [ @@ -148,6 +158,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => false, 'checked' => false, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'An installed non uninstallable extension should be checked and disabled' => [ @@ -169,6 +181,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => true, 'checked' => true, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'An installed non uninstallable extension should be enabled if the "disable uninstallation check" flag is set' => [ @@ -190,6 +204,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => false, 'checked' => true, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'A mandatory extension should be checked and disabled' => [ @@ -211,6 +227,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => false, 'disabled' => true, 'checked' => true, + 'dependency_issue' => false, + 'mandatory' => true, ], ], 'A mandatory extension should be checked and disabled even if the "disable uninstallation check" flag is set' => [ @@ -232,6 +250,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => false, 'disabled' => true, 'checked' => true, + 'dependency_issue' => false, + 'mandatory' => true, ], ], 'An optional sub extension should not force its parent flags' => [ @@ -265,6 +285,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => false, 'disabled' => false, 'checked' => false, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'A mandatory sub extension should force its parent to be checked and disabled' => [ @@ -298,6 +320,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => false, 'disabled' => true, 'checked' => true, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'An installed non uninstallable sub extension should force its parent to be checked and disabled' => [ @@ -331,6 +355,8 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => true, 'checked' => true, + 'dependency_issue' => false, + 'mandatory' => false, ], ], 'A non installed non uninstallable sub extension should not force its parent flags' => [ @@ -364,6 +390,62 @@ class WizStepModulesChoiceTest extends ItopTestCase 'installed' => true, 'disabled' => false, 'checked' => false, + 'dependency_issue' => false, + 'mandatory' => false, + ], + ], + 'A non installed extension with missing dependencies should be not checked and disabled' => [ + 'aExtensionsOnDiskOrDb' => [ + 'itop-ext1' => [ + 'installed' => false, + 'missing_dependencies' => [ + 'itop-ext1-1', + ], + ], + ], + 'aWizardStepDefinition' => [ + 'extension_code' => 'itop-ext1', + 'mandatory' => false, + 'uninstallable' => true, + 'missing_dependencies' => true, + ], + 'bCurrentSelected' => false, + 'bDisableUninstallChecks' => false, + 'aExpectedFlags' => [ + 'uninstallable' => true, + 'missing' => false, + 'installed' => false, + 'disabled' => true, + 'checked' => false, + 'dependency_issue' => true, + 'mandatory' => false, + ], + ], + 'An installed extension with missing dependencies should be not checked and disabled' => [ + 'aExtensionsOnDiskOrDb' => [ + 'itop-ext1' => [ + 'installed' => true, + 'missing_dependencies' => [ + 'itop-ext1-1', + ], + ], + ], + 'aWizardStepDefinition' => [ + 'extension_code' => 'itop-ext1', + 'mandatory' => false, + 'uninstallable' => true, + 'missing_dependencies' => true, + ], + 'bCurrentSelected' => false, + 'bDisableUninstallChecks' => false, + 'aExpectedFlags' => [ + 'uninstallable' => true, + 'missing' => false, + 'installed' => true, + 'disabled' => true, + 'checked' => false, + 'dependency_issue' => true, + 'mandatory' => false, ], ], ]; diff --git a/tests/php-unit-tests/unitary-tests/setup/iTopExtensionsMapFake.php b/tests/php-unit-tests/unitary-tests/setup/iTopExtensionsMapFake.php index 8dbf59085c..ecc68c5ab5 100644 --- a/tests/php-unit-tests/unitary-tests/setup/iTopExtensionsMapFake.php +++ b/tests/php-unit-tests/unitary-tests/setup/iTopExtensionsMapFake.php @@ -23,6 +23,7 @@ class iTopExtensionsMapFake extends iTopExtensionsMap $oExtension->sVersion = $aExtension['version'] ?? '1.0.0'; $oExtension->sSource = $aExtension['source'] ?? iTopExtension::SOURCE_MANUAL; $oExtension->aModuleInfo = $aExtension['module_info'] ?? []; + $oExtension->aMissingDependencies = $aExtension['missing_dependencies'] ?? []; $oMap->AddExtension($oExtension); } return $oMap;