N°8762 - Allow extensions uninstallation at setup

This commit is contained in:
Timmy38
2025-11-05 15:52:03 +01:00
committed by GitHub
parent c5fb116052
commit d4183acfde
6 changed files with 112 additions and 68 deletions

View File

@@ -110,6 +110,21 @@ class iTopExtension
$this->bVisible = true; $this->bVisible = true;
$this->aMissingDependencies = array(); $this->aMissingDependencies = array();
} }
/**
* @since 3.3.0
* @return bool
*/
public function CanBeUninstalled()
{
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
$bUninstallable = $aModuleInfo['uninstallable'] === 'yes';
if (!$bUninstallable) {
return false;
}
}
return true;
}
} }
/** /**
@@ -253,6 +268,22 @@ class iTopExtensionsMap
$this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension; $this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
} }
/**
* @since 3.3.0
* @param string $sExtensionCode
*
* @return \iTopExtension|null
*/
public function Get(string $sExtensionCode):?iTopExtension
{
foreach($this->aExtensions as $oExtension) {
if ($oExtension->sCode === $sExtensionCode) {
return $oExtension;
}
}
return null;
}
/** /**
* Read (recursively) a directory to find if it contains extensions (or modules) * Read (recursively) a directory to find if it contains extensions (or modules)
* *
@@ -277,8 +308,7 @@ class iTopExtensionsMap
$aSubDirectories = array(); $aSubDirectories = array();
// First check if there is an extension.xml file in this directory // First check if there is an extension.xml file in this directory
if (is_readable($sSearchDir.'/extension.xml')) if (is_readable($sSearchDir.'/extension.xml')) {
{
$oXml = new XMLParameters($sSearchDir.'/extension.xml'); $oXml = new XMLParameters($sSearchDir.'/extension.xml');
$oExtension = new iTopExtension(); $oExtension = new iTopExtension();
$oExtension->sCode = $oXml->Get('extension_code'); $oExtension->sCode = $oXml->Get('extension_code');
@@ -315,28 +345,27 @@ class iTopExtensionsMap
// If we are not already inside a formal extension, then the module itself is considered // If we are not already inside a formal extension, then the module itself is considered
// as an extension, otherwise, the module is just added to the list of modules belonging // as an extension, otherwise, the module is just added to the list of modules belonging
// to this extension // to this extension
$sModuleId = $aModuleInfo[1]; $sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId); list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
if ($sModuleVersion == '') if ($sModuleVersion == '') {
{
// Provide a default module version since version is mandatory when recording ExtensionInstallation // Provide a default module version since version is mandatory when recording ExtensionInstallation
$sModuleVersion = '0.0.1'; $sModuleVersion = '0.0.1';
} }
$aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['uninstallable'] ??= 'yes';
if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension)) { if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension)) {
// Already inside an extension, let's add this module the list of modules belonging to this extension // Already inside an extension, let's add this module the list of modules belonging to this extension
$this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName; $this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName;
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion; $this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[2]; $this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
} }
else else {
{
// Not already inside an folder containing an 'extension.xml' file // Not already inside an folder containing an 'extension.xml' file
// Ignore non-visible modules and auto-select ones, since these are never prompted // Ignore non-visible modules and auto-select ones, since these are never prompted
// as a choice to the end-user // as a choice to the end-user
$bVisible = true; $bVisible = true;
if (!$aModuleInfo[2]['visible'] || isset($aModuleInfo[2]['auto_select'])) if (!$aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['visible'] || isset($aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['auto_select']))
{ {
$bVisible = false; $bVisible = false;
} }
@@ -344,15 +373,15 @@ class iTopExtensionsMap
// Let's create a "fake" extension from this module (containing just this module) for backwards compatibility // Let's create a "fake" extension from this module (containing just this module) for backwards compatibility
$oExtension = new iTopExtension(); $oExtension = new iTopExtension();
$oExtension->sCode = $sModuleName; $oExtension->sCode = $sModuleName;
$oExtension->sLabel = $aModuleInfo[2]['label']; $oExtension->sLabel = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['label'];
$oExtension->sDescription = ''; $oExtension->sDescription = '';
$oExtension->sVersion = $sModuleVersion; $oExtension->sVersion = $sModuleVersion;
$oExtension->sSource = $sSource; $oExtension->sSource = $sSource;
$oExtension->bMandatory = $aModuleInfo[2]['mandatory']; $oExtension->bMandatory = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['mandatory'];
$oExtension->sMoreInfoUrl = $aModuleInfo[2]['doc.more_information']; $oExtension->sMoreInfoUrl = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['doc.more_information'];
$oExtension->aModules = array($sModuleName); $oExtension->aModules = array($sModuleName);
$oExtension->aModuleVersion[$sModuleName] = $sModuleVersion; $oExtension->aModuleVersion[$sModuleName] = $sModuleVersion;
$oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[2]; $oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
$oExtension->sSourceDir = $sSearchDir; $oExtension->sSourceDir = $sSearchDir;
$oExtension->bVisible = $bVisible; $oExtension->bVisible = $bVisible;
$this->AddExtension($oExtension); $this->AddExtension($oExtension);
@@ -452,6 +481,7 @@ class iTopExtensionsMap
} }
} }
/** /**
* Tells if a given extension(code) is marked as chosen * Tells if a given extension(code) is marked as chosen
* @param string $sExtensionCode * @param string $sExtensionCode
@@ -572,30 +602,24 @@ class iTopExtensionsMap
public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MANUAL) public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MANUAL)
{ {
$aSignatures = $this->GetOldExtensionsSignatures(); $aSignatures = $this->GetOldExtensionsSignatures();
foreach($aSignatures as $sExtensionCode => $aExtensionSignatures) foreach($aSignatures as $sExtensionCode => $aExtensionSignatures) {
{
$bFound = false; $bFound = false;
foreach($aExtensionSignatures['versions'] as $sVersion => $aModules) foreach($aExtensionSignatures['versions'] as $sVersion => $aModules) {
{
$bInstalled = true; $bInstalled = true;
foreach($aModules as $sModuleId) foreach($aModules as $sModuleId) {
{ if(!$this->ModuleIsPresent($sModuleId, $sInSourceOnly)) {
if(!$this->ModuleIsPresent($sModuleId, $sInSourceOnly))
{
$bFound = false; $bFound = false;
break; // One missing module is enough to determine that the extension/version is not present break; // One missing module is enough to determine that the extension/version is not present
} }
else else {
{ $bInstalled = $bInstalled && $this->ModuleIsInstalled($sModuleId, $sInSourceOnly);
$bInstalled = $bInstalled && (!$this->ModuleIsInstalled($sModuleId, $sInSourceOnly));
$bFound = true; $bFound = true;
} }
} }
if ($bFound) break; // The current version matches the signature if ($bFound) break; // The current version matches the signature
} }
if ($bFound) if ($bFound) {
{
$oExtension = new iTopExtension(); $oExtension = new iTopExtension();
$oExtension->sCode = $sExtensionCode; $oExtension->sCode = $sExtensionCode;
$oExtension->sLabel = $aExtensionSignatures['label']; $oExtension->sLabel = $aExtensionSignatures['label'];
@@ -603,15 +627,14 @@ class iTopExtensionsMap
$oExtension->sDescription = $aExtensionSignatures['description']; $oExtension->sDescription = $aExtensionSignatures['description'];
$oExtension->sVersion = $sVersion; $oExtension->sVersion = $sVersion;
$oExtension->aModules = array(); $oExtension->aModules = array();
if ($bInstalled) if ($bInstalled) {
{
$oExtension->sInstalledVersion = $sVersion; $oExtension->sInstalledVersion = $sVersion;
$oExtension->bMarkedAsChosen = true; $oExtension->bMarkedAsChosen = true;
} }
foreach($aModules as $sModuleId) foreach($aModules as $sModuleId) {
{
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId); list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
$oExtension->aModules[] = $sModuleName; $oExtension->aModules[] = $sModuleName;
$oExtension->aModuleInfo[$sModuleName] = $this->aExtensions[$sModuleId]->aModuleInfo[$sModuleName];
} }
$this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension); $this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension);
} }

View File

@@ -520,7 +520,7 @@ class ModuleDiscovery
$sModuleFilePath = $sDirectory.'/'.$sFile; $sModuleFilePath = $sDirectory.'/'.$sFile;
try { try {
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sDirectory.'/'.$sFile); $aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sDirectory.'/'.$sFile);
SetupWebPage::AddModule($sModuleFilePath, $aModuleInfo[1], $aModuleInfo[2]); SetupWebPage::AddModule($sModuleFilePath, $aModuleInfo[ModuleFileReader::MODULE_INFO_ID], $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]);
} catch(ModuleFileReaderException $e){ } catch(ModuleFileReaderException $e){
continue; continue;
} }

View File

@@ -32,6 +32,10 @@ class ModuleFileReader {
"method_exists" "method_exists"
]; ];
const MODULE_INFO_PATH = 0;
const MODULE_INFO_ID = 1;
const MODULE_INFO_CONFIG = 2;
const STATIC_CALLWHITELIST=[ const STATIC_CALLWHITELIST=[
"utils::GetItopVersionWikiSyntax" "utils::GetItopVersionWikiSyntax"
]; ];
@@ -168,7 +172,7 @@ class ModuleFileReader {
private function CompleteModuleInfoWithFilePath(array &$aModuleInfo) private function CompleteModuleInfoWithFilePath(array &$aModuleInfo)
{ {
if (count($aModuleInfo)==3) { if (count($aModuleInfo)==3) {
$aModuleInfo[2]['module_file_path'] = $aModuleInfo[0]; $aModuleInfo[static::MODULE_INFO_CONFIG]['module_file_path'] = $aModuleInfo[static::MODULE_INFO_PATH];
} }
} }
@@ -255,9 +259,9 @@ class ModuleFileReader {
} }
return [ return [
$sModuleFilePath, static::MODULE_INFO_PATH => $sModuleFilePath,
$sModuleId, static::MODULE_INFO_ID => $sModuleId,
$aModuleConfig, static::MODULE_INFO_CONFIG => $aModuleConfig,
]; ];
} }

View File

@@ -48,7 +48,7 @@ class ModuleInstallation extends DBObject
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values" => null, "sql" => "installed", "default_value" => null, "is_null_allowed" => true, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values" => null, "sql" => "installed", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeText("comment", array("allowed_values" => null, "sql" => "comment", "default_value" => null, "is_null_allowed" => true, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeText("comment", array("allowed_values" => null, "sql" => "comment", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass" => "ModuleInstallation", "jointype" => "", "allowed_values" => null, "sql" => "parent_id", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass" => "ModuleInstallation", "jointype" => "", "allowed_values" => null, "sql" => "parent_id", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("uninstallable", array("allowed_values"=>new ValueSetEnum('yes,no,maybe'), "sql"=>"uninstallable", "default_value"=>'yes', "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', array('name', 'version', 'installed', 'comment', 'parent_id')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('name', 'version', 'installed', 'comment', 'parent_id')); // Attributes to be displayed for the complete details
@@ -87,6 +87,7 @@ class ExtensionInstallation extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("label", array("allowed_values"=>null, "sql"=>"label", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("label", array("allowed_values"=>null, "sql"=>"label", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("source", array("allowed_values"=>null, "sql"=>"source", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("source", array("allowed_values"=>null, "sql"=>"source", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("uninstallable", array("allowed_values"=>new ValueSetEnum('yes,no,maybe'), "sql"=>"uninstallable", "default_value"=>'yes', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values"=>null, "sql"=>"installed", "default_value"=>'NOW()', "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values"=>null, "sql"=>"installed", "default_value"=>'NOW()', "is_null_allowed"=>false, "depends_on"=>array())));

View File

@@ -756,6 +756,7 @@ class RunTimeEnvironment
$aModuleData = $aAvailableModules[$sModuleId]; $aModuleData = $aAvailableModules[$sModuleId];
$sName = $sModuleId; $sName = $sModuleId;
$sVersion = $aModuleData['version_code']; $sVersion = $aModuleData['version_code'];
$sUninstallable = $aModuleData['uninstallable'] ?? 'yes';
$aComments = array(); $aComments = array();
$aComments[] = $sShortComment; $aComments[] = $sShortComment;
if ($aModuleData['mandatory']) { if ($aModuleData['mandatory']) {
@@ -783,6 +784,7 @@ class RunTimeEnvironment
$oInstallRec->Set('comment', $sComment); $oInstallRec->Set('comment', $sComment);
$oInstallRec->Set('parent_id', $iMainItopRecord); $oInstallRec->Set('parent_id', $iMainItopRecord);
$oInstallRec->Set('installed', $iInstallationTime); $oInstallRec->Set('installed', $iInstallationTime);
$oInstallRec->Set('uninstallable', $sUninstallable);
$oInstallRec->DBInsertNoReload(); $oInstallRec->DBInsertNoReload();
} }
@@ -805,6 +807,7 @@ class RunTimeEnvironment
$oInstallRec->Set('label', $oExtension->sLabel); $oInstallRec->Set('label', $oExtension->sLabel);
$oInstallRec->Set('version', $oExtension->sVersion); $oInstallRec->Set('version', $oExtension->sVersion);
$oInstallRec->Set('source', $oExtension->sSource); $oInstallRec->Set('source', $oExtension->sSource);
$oInstallRec->Set('uninstallable', $oExtension->CanBeUninstalled() ? 'yes' : 'no');
$oInstallRec->Set('installed', $iInstallationTime); $oInstallRec->Set('installed', $iInstallationTime);
$oInstallRec->DBInsertNoReload(); $oInstallRec->DBInsertNoReload();
} }

View File

@@ -997,6 +997,26 @@ JS
); );
} }
} }
final protected function AddForceUninstallFlagOption(WebPage $oPage): void
{
$sChecked = $this->oWizard->GetParameter('force-uninstall', false) ? ' checked ' : '';
$oPage->add('<fieldset>');
$oPage->add('<legend>Advanced parameters</legend>');
$oPage->p('<input id="force-uninstall" type="checkbox"'.$sChecked.' name="force-uninstall"><label for="force-uninstall">&nbsp;Disable uninstallation checks for extensions');
$oPage->add('</fieldset>');
$oPage->add_ready_script(<<<'JS'
$("#force-uninstall").on("click", function() {
let $this = $(this);
let bForceUninstall = $this.prop("checked");
if( bForceUninstall && !confirm('Beware, uninstalling extensions flagged as non uninstallable may result in data corruption and application crashes. Are you sure you want to continue ?')){
$this.prop("checked",false);
}
});
JS
);
}
} }
@@ -1181,6 +1201,7 @@ class WizStepUpgradeMiscParams extends AbstractWizStepMiscParams
{ {
$this->oWizard->SaveParameter('application_url', ''); $this->oWizard->SaveParameter('application_url', '');
$this->oWizard->SaveParameter('graphviz_path', ''); $this->oWizard->SaveParameter('graphviz_path', '');
$this->oWizard->SaveParameter('force-uninstall', false);
return array('class' => 'WizStepModulesChoice', 'state' => 'start_upgrade'); return array('class' => 'WizStepModulesChoice', 'state' => 'start_upgrade');
} }
@@ -1223,6 +1244,7 @@ EOF
); );
$this->AddUseSymlinksFlagOption($oPage); $this->AddUseSymlinksFlagOption($oPage);
$this->AddForceUninstallFlagOption($oPage);
} }
public function AsyncAction(WebPage $oPage, $sCode, $aParameters) public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
@@ -1436,6 +1458,7 @@ class WizStepModulesChoice extends WizardStep
$oPage->add_style("div.choice a { text-decoration:none; font-weight: bold; color: #1C94C4 }"); $oPage->add_style("div.choice a { text-decoration:none; font-weight: bold; color: #1C94C4 }");
$oPage->add_style("div.description { margin-left: 2em; }"); $oPage->add_style("div.description { margin-left: 2em; }");
$oPage->add_style(".choice-disabled { color: #999; }"); $oPage->add_style(".choice-disabled { color: #999; }");
$oPage->add_style("input.unremovable { accent-color: orangered;}");
$aModules = SetupUtils::AnalyzeInstallation($this->oWizard); $aModules = SetupUtils::AnalyzeInstallation($this->oWizard);
$sManualInstallError = SetupUtils::CheckManualInstallDirEmpty($aModules, $sManualInstallError = SetupUtils::CheckManualInstallDirEmpty($aModules,
@@ -1931,7 +1954,7 @@ EOF
if (@file_exists($this->GetSourceFilePath())) if (@file_exists($this->GetSourceFilePath()))
{ {
// Found an "installation.xml" file, let's us tis definition for the wizard // Found an "installation.xml" file, let's use this definition for the wizard
$aParams = new XMLParameters($this->GetSourceFilePath()); $aParams = new XMLParameters($this->GetSourceFilePath());
$aSteps = $aParams->Get('steps', array()); $aSteps = $aParams->Get('steps', array());
@@ -2031,55 +2054,45 @@ EOF
{ {
$aOptions = isset($aStepInfo['options']) ? $aStepInfo['options'] : array(); $aOptions = isset($aStepInfo['options']) ? $aStepInfo['options'] : array();
$aAlternatives = isset($aStepInfo['alternatives']) ? $aStepInfo['alternatives'] : array(); $aAlternatives = isset($aStepInfo['alternatives']) ? $aStepInfo['alternatives'] : array();
$index = 0;
$sAllDisabled = ''; $bDisableUninstallCheck = (bool)$this->oWizard->GetParameter('force-uninstall', false);
if ($bAllDisabled) {
$sAllDisabled = 'disabled data-disabled="disabled" ';
}
foreach ($aOptions as $index => $aChoice) { foreach ($aOptions as $index => $aChoice) {
$sAttributes = '';
$sChoiceId = $sParentId.self::$SEP.$index; $sChoiceId = $sParentId.self::$SEP.$index;
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"'; $sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
$sId = utils::EscapeHtml($aChoice['extension_code']); $sId = utils::EscapeHtml($aChoice['extension_code']);
$bIsDefault = array_key_exists($sChoiceId, $aDefaults); $bIsDefault = array_key_exists($sChoiceId, $aDefaults);
$bCanBeUninstalled = $this->oExtensionsMap->Get($aChoice['extension_code'])->CanBeUninstalled();
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId); $bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault); $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $this->bUpgrade && $bIsDefault && !$bCanBeUninstalled && !$bDisableUninstallCheck;;
$bDisabled = false; $bDisabled = $bMandatory || $bAllDisabled;
if ($bMandatory) { $bChecked = $bMandatory || $bSelected;
$oPage->add('<div class="choice" '.$sDataId.'><input id="'.$sId.'" checked disabled data-disabled="disabled" type="checkbox"'.$sAttributes.'/><input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'">&nbsp;'); $sChecked = $bChecked ? ' checked ' : '';
$bDisabled = true; $sDisabled = $bDisabled ? ' disabled data-disabled="disabled" ' : '';
} else if ($bSelected) { $sUnremovable = !$bCanBeUninstalled ? ' unremovable ' : '';
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" '.$sAllDisabled.'id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" checked value="'.$sChoiceId.'"/>&nbsp;'); $sHiddenInput = $bDisabled && $bChecked ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
} else { $oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.'&nbsp;');
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" '.$sAllDisabled.'id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'"/>&nbsp;'); $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $bCanBeUninstalled);
}
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled);
$oPage->add('</div>'); $oPage->add('</div>');
$index++;
} }
$sChoiceName = null; $sChoiceName = null;
$sDisabled = ''; $sDisabled = '';
$bDisabled = false; $bDisabled = false;
$sChoiceIdNone = null; $sChoiceIdNone = null;
foreach($aAlternatives as $index => $aChoice) foreach($aAlternatives as $index => $aChoice) {
{
$sChoiceId = $sParentId.self::$SEP.$index; $sChoiceId = $sParentId.self::$SEP.$index;
if ($sChoiceName == null) if ($sChoiceName == null) {
{
$sChoiceName = $sChoiceId; // All radios share the same name $sChoiceName = $sChoiceId; // All radios share the same name
} }
$bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId); $bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId);
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault); $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault);
if ($bMandatory || $bAllDisabled) if ($bMandatory || $bAllDisabled) {
{
// One choice is mandatory, all alternatives are disabled // One choice is mandatory, all alternatives are disabled
$sDisabled = ' disabled data-disabled="disabled"'; $sDisabled = ' disabled data-disabled="disabled"';
$bDisabled = true; $bDisabled = true;
} }
if ( (!isset($aChoice['sub_options']) || (count($aChoice['sub_options']) == 0)) && (!isset($aChoice['modules']) || (count($aChoice['modules']) == 0)) ) if ( (!isset($aChoice['sub_options']) || (count($aChoice['sub_options']) == 0)) && (!isset($aChoice['modules']) || (count($aChoice['modules']) == 0)) ) {
{
$sChoiceIdNone = $sChoiceId; // the "None" / empty choice $sChoiceIdNone = $sChoiceId; // the "None" / empty choice
} }
} }
@@ -2111,24 +2124,24 @@ EOF
$sAttributes = ' checked '; $sAttributes = ' checked ';
} }
$sHidden = ''; $sHidden = '';
if ($bMandatory && $bDisabled) if ($bMandatory && $bDisabled) {
{
$sAttributes = ' checked '; $sAttributes = ' checked ';
$sHidden = '<input type="hidden" name="choice['.$sChoiceName.']" value="'.$sChoiceId.'"/>'; $sHidden = '<input type="hidden" name="choice['.$sChoiceName.']" value="'.$sChoiceId.'"/>';
} }
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" id="'.$sId.'" name="choice['.$sChoiceName.']" type="radio"'.$sAttributes.' value="'.$sChoiceId.'"'.$sDisabled.'/>'.$sHidden.'&nbsp;'); $oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" id="'.$sId.'" name="choice['.$sChoiceName.']" type="radio"'.$sAttributes.' value="'.$sChoiceId.'"'.$sDisabled.'/>'.$sHidden.'&nbsp;');
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled && !$bSelected); $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled && !$bSelected);
$oPage->add('</div>'); $oPage->add('</div>');
$index++;
} }
} }
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false) protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $bUninstallable = true)
{ {
$sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '<a class="setup--wizard-choice--more-info" target="_blank" href="'.$aChoice['more_info'].'">More information</a>' : ''; $sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '<a class="setup--wizard-choice--more-info" target="_blank" href="'.$aChoice['more_info'].'">More information</a>' : '';
$sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : ''; $sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : '';
$sId = utils::EscapeHtml($aChoice['extension_code']); $sId = utils::EscapeHtml($aChoice['extension_code']);
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.'</label> '.$sMoreInfo); $sUninstallationWarning = $bUninstallable ? '' : '<span style="color:orangered" title="Once this extension has been installed, it cannot be removed">(!)</span>';
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.'</label>&nbsp;'.$sUninstallationWarning.' '.$sMoreInfo.'');
$sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : ''; $sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : '';
$oPage->add('<div class="setup--wizard-choice--description description">'.$sDescription.'<span id="sub_choices'.$sId.'">'); $oPage->add('<div class="setup--wizard-choice--description description">'.$sDescription.'<span id="sub_choices'.$sId.'">');
if (isset($aChoice['sub_options'])) { if (isset($aChoice['sub_options'])) {