N°9010 Setup wizard : manage multiple level extension choice

This commit is contained in:
Timothee
2025-12-23 16:05:23 +01:00
committed by Eric Espie
parent b26e0c8a90
commit 2fcd224ffd
4 changed files with 351 additions and 24 deletions

View File

@@ -1562,7 +1562,7 @@ EOF
}
}
$aAlternatives = isset($aInfo['alternatives']) ? $aInfo['alternatives'] : [];
$aAlternatives = $aInfo['alternatives'] ?? [];
$sChoiceName = null;
foreach ($aAlternatives as $index => $aChoice) {
$sChoiceId = $sParentId.self::$SEP.$index;
@@ -1953,10 +1953,47 @@ EOF
return '<i class="setup-extension--icon '.$sDecorationClass.'" data-tooltip-content="'.$sResult.'"></i>';
}
public function ComputeChoiceFlags(array $aChoice, string $sChoiceId, array $aSelectedComponents, bool $bAllDisabled, bool $bDisableUninstallCheck, bool $bUpgradeMode)
{
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] === true || $aChoice['uninstallable'] === 'yes' : $oITopExtension->CanBeUninstalled();
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $bUpgradeMode && $oITopExtension->bInstalled && !$bCanBeUninstalled && !$bDisableUninstallCheck;
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
$bChecked = $bMandatory || $bSelected;
if (isset($aChoice['sub_options'])) {
$aOptions = $aChoice['sub_options']['options'] ?? [];
foreach ($aOptions as $index => $aSubChoice) {
$sSubChoiceId = $sChoiceId.self::$SEP.$index;
$aSubFlags = $this->ComputeChoiceFlags($aSubChoice, $sSubChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $bUpgradeMode);
if ($aSubFlags['checked']) {
$bChecked = true;
if ($aSubFlags['disabled']) {
//If some sub options are enabled and cannot be disabled, this choice should also cannot be disabled since it would disable all its sub options
$bDisabled = true;
}
}
}
}
return [
'uninstallable' => $bCanBeUninstalled,
'missing' => $bMissingFromDisk,
'installed' => $bInstalled,
'disabled' => $bDisabled,
'checked' => $bChecked,
];
}
protected function DisplayOptions($oPage, $aStepInfo, $aSelectedComponents, $aDefaults, $sParentId = '', $bAllDisabled = false)
{
$aOptions = isset($aStepInfo['options']) ? $aStepInfo['options'] : [];
$aAlternatives = isset($aStepInfo['alternatives']) ? $aStepInfo['alternatives'] : [];
$aOptions = $aStepInfo['options'] ?? [];
$aAlternatives = $aStepInfo['alternatives'] ?? [];
$bDisableUninstallCheck = (bool)$this->oWizard->GetParameter('force-uninstall', false);
@@ -1964,43 +2001,33 @@ EOF
$sChoiceId = $sParentId.self::$SEP.$index;
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
$sId = utils::EscapeHtml($aChoice['extension_code']);
$bIsDefault = array_key_exists($sChoiceId, $aDefaults);
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] : $oITopExtension->CanBeUninstalled();
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $this->bUpgrade && $bIsDefault && !$bCanBeUninstalled && !$bDisableUninstallCheck;
;
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
$bChecked = $bMandatory || $bSelected;
$aFlags = static::ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade);
$sTooltip = '';
$sUnremovable = '';
if ($bMissingFromDisk) {
if ($aFlags['missing']) {
$sTooltip .= '<span class="setup-extension-tag removed">source removed</span>';
}
if ($bInstalled) {
if ($aFlags['installed']) {
$sTooltip .= '<span class="setup-extension-tag checked installed">installed</span>';
$sTooltip .= '<span class="setup-extension-tag unchecked tobeuninstalled">to be uninstalled</span>';
} else {
$sTooltip .= '<span class="setup-extension-tag checked tobeinstalled">to be installed</span>';
$sTooltip .= '<span class="setup-extension-tag unchecked notinstalled">not installed</span>';
}
if (!$bCanBeUninstalled) {
if (!$aFlags['uninstallable']) {
$sTooltip .= '<span class="setup-extension-tag notuninstallable">cannot be uninstalled</span>';
}
if ($bDisabled && !$bChecked && !$bCanBeUninstalled && !$bDisableUninstallCheck) {
if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) {
$this->bCanMoveForward = false;//Disable "Next"
}
$sChecked = $bChecked ? ' checked ' : '';
$sDisabled = $bDisabled ? ' disabled data-disabled="disabled" ' : '';
$sMissingModule = $bMissingFromDisk ? 'setup-extension--missing' : '';
$sChecked = $aFlags['checked'] ? ' checked ' : '';
$sDisabled = $aFlags['disabled'] ? ' disabled data-disabled="disabled" ' : '';
$sMissingModule = $aFlags['missing'] ? 'setup-extension--missing' : '';
$sHiddenInput = $bDisabled && $bChecked ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
$sHiddenInput = $aFlags['disabled'] && $aFlags['checked'] ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
$oPage->add('<div class="choice '.$sMissingModule.'" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.'&nbsp;');
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $sTooltip);
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $aFlags['disabled'], $sTooltip);
$oPage->add('</div>');
}
$sChoiceName = null;
@@ -2035,7 +2062,6 @@ EOF
$sChoiceId = $sParentId.self::$SEP.$index;
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
$sId = utils::EscapeHtml($aChoice['extension_code']);
if ($sChoiceName == null) {
$sChoiceName = $sChoiceId; // All radios share the same name
}

View File

@@ -0,0 +1,14 @@
<?php
class WizStepModulesChoiceFake extends WizStepModulesChoice
{
public function __construct(WizardController $oWizard, $sCurrentState)
{
}
public function setExtensionMap(iTopExtensionsMap $oMap)
{
$this->oExtensionsMap = $oMap;
}
}

View File

@@ -0,0 +1,260 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Integration;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use ItopExtensionsMap;
use iTopExtensionsMapFake;
use ModuleDiscovery;
use WizardController;
class WizStepModulesChoiceTest extends ItopTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('/setup/unattended-install/InstallationFileService.php');
require_once __DIR__.'/iTopExtensionsMapFake.php';
require_once __DIR__.'/WizStepModulesChoiceFake.php';
$this->oStep = new \WizStepModulesChoiceFake(new WizardController('', ''), '');
ModuleDiscovery::ResetCache();
}
public function ProviderComputeChoiceFlags()
{
return [
'selected but not installed extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => false,
],
],
'bUpgrade' => false,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
],
'aSelected' => ['_0' => '_0'],
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => false,
'disabled' => false,
'checked' => true,
],
],
'not selected, not installed extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => false,
],
],
'bUpgrade' => true,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
],
'aSelected' => [],
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => false,
'disabled' => false,
'checked' => false,
],
],
'installed extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => true,
],
],
'bUpgrade' => true,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
],
'aSelected' => [],
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => true,
'disabled' => false,
'checked' => false,
],
],
'installed non uninstallable extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => true,
],
],
'bUpgrade' => true,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => false,
],
'aSelected' => [],
'aExpectedFlags' => [
'uninstallable' => false,
'missing' => false,
'installed' => true,
'disabled' => true,
'checked' => true,
],
],
'mandatory extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => false,
],
],
'bUpgrade' => true,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => true,
'uninstallable' => true,
],
'aSelected' => [],
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => false,
'disabled' => true,
'checked' => true,
],
],
'optional sub extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => false,
],
'itop-ext1-1' => [
'installed' => false,
],
],
'bUpgrade' => true,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
'sub_options' => [
'options' => [
[
'extension_code' => 'itop-ext1-1',
'mandatory' => false,
'uninstallable' => true,
],
],
],
],
'aSelected' => [],
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => false,
'disabled' => false,
'checked' => false,
],
],
'mandatory sub extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => false,
],
'itop-ext1-1' => [
'installed' => false,
],
],
'bUpgrade' => true,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
'sub_options' => [
'options' => [
[
'extension_code' => 'itop-ext1-1',
'mandatory' => true,
'uninstallable' => true,
],
],
],
],
'aSelected' => [],
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => false,
'disabled' => true,
'checked' => true,
],
],
'non uninstallable sub extension' => [
'aExtensions' => [
'itop-ext1' => [
'installed' => true,
],
'itop-ext1-1' => [
'installed' => true,
],
],
'bUpgrade' => true,
'bDisableUninstallCheck' => false,
'sChoiceId' => '_0',
'aStepInfo' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
'sub_options' => [
'options' => [
[
'extension_code' => 'itop-ext1-1',
'mandatory' => false,
'uninstallable' => false,
],
],
],
],
'aSelected' => [],
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => true,
'disabled' => true,
'checked' => true,
],
],
];
}
/**
* @dataProvider ProviderComputeChoiceFlags
*/
public function testComputeChoiceFlags($aExtensions, $bUpgrade, $bDisableUninstallCheck, $sChoiceId, $aStepInfo, $aSelected, $aExpectedFlags)
{
$this->oStep->setExtensionMap(iTopExtensionsMapFake::createFromArray($aExtensions));
$aFlags = $this->oStep->ComputeChoiceFlags($aStepInfo, $sChoiceId, $aSelected, false, $bDisableUninstallCheck, $bUpgrade);
$this->assertEquals($aExpectedFlags, $aFlags);
}
}

View File

@@ -0,0 +1,27 @@
<?php
class iTopExtensionsMapFake extends iTopExtensionsMap
{
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
{
$this->aExtensions = [];
$this->aExtensionsByCode = [];
$this->aScannedDirs = [];
}
public static function createFromArray($aExtensions)
{
$oMap = new static();
foreach ($aExtensions as $sCode => $aExtension) {
$oExtension = new iTopExtension();
$oExtension->sCode = $sCode;
$oExtension->bInstalled = $aExtension['installed'];
$oExtension->aModules = $aExtension['modules'] ?? [];
$oExtension->bCanBeUninstalled = $aExtension['uninstallable'] ?? null;
$oExtension->sVersion = $aExtension['version'] ?? '1.0.0';
$oExtension->aModuleInfo = [];
$oMap->AddExtension($oExtension);
}
return $oMap;
}
}