bChoicesFromDatabase = false; $this->oExtensionsMap = new iTopExtensionsMap(); $sPreviousSourceDir = $this->oWizard->GetParameter('previous_version_dir', ''); $sConfigPath = null; if (($sPreviousSourceDir !== '') && is_readable($sPreviousSourceDir.'/conf/production/config-itop.php')) { $sConfigPath = $sPreviousSourceDir.'/conf/production/config-itop.php'; } elseif (is_readable(utils::GetConfigFilePath('production'))) { $sConfigPath = utils::GetConfigFilePath('production'); } // only called if the config file exists : we are updating a previous installation ! // WARNING : we can't load this config directly, as it might be from another directory with a different approot_url (N°2684) if ($sConfigPath !== null) { $this->oConfig = new Config($sConfigPath); $aParamValues = $oWizard->GetParamForConfigArray(); $this->oConfig->UpdateFromParams($aParamValues); $this->oExtensionsMap->LoadChoicesFromDatabase($this->oConfig); $this->bChoicesFromDatabase = true; } // Sanity check (not stopper, to let developers go further...) try { $this->aAnalyzeInstallationModules = SetupUtils::AnalyzeInstallation($this->oWizard, true); } catch (MissingDependencyException $e) { $this->oMissingDependencyException = $e; $this->aAnalyzeInstallationModules = SetupUtils::AnalyzeInstallation($this->oWizard); } } public function GetTitle(): string { $aStepInfo = $this->GetStepInfo(); return $aStepInfo['title'] ?? 'Modules selection'; } public function GetPossibleSteps() { return [WizStepModulesChoice::class, WizStepDataAudit::class, WizStepSummary::class]; } public function GetAddedAndRemovedExtensions($aSelectedExtensions) { $aExtensionsAdded = []; $aExtensionsRemoved = []; $aExtensionsNotUninstallable = []; foreach ($this->oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) { /* @var \iTopExtension $oExtension */ $bSelected = in_array($oExtension->sCode, $aSelectedExtensions); if ($oExtension->bInstalled && !$bSelected) { $aExtensionsRemoved[$oExtension->sCode] = $oExtension->sLabel; if (!$oExtension->CanBeUninstalled()) { $aExtensionsNotUninstallable[$oExtension->sCode] = true; } } elseif (!$oExtension->bInstalled && $bSelected) { $aExtensionsAdded[$oExtension->sCode] = $oExtension->sLabel; } } return [$aExtensionsAdded, $aExtensionsRemoved, $aExtensionsNotUninstallable]; } public function IsDataAuditEnabled(): bool { $sPath = APPROOT.'env-production'; if (!is_dir($sPath)) { SetupLog::Info("Reinstallation of an iTop from a backup (No env-production found). Setup data audit disabled"); return false; } return true; } public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState { // Accumulates the selected modules: $index = $this->GetStepIndex(); // use json_encode:decode to store a hash array: step_id => array(input_name => selected_input_id) $aSelectedChoices = json_decode($this->oWizard->GetParameter('selected_components', '{}'), true); $aSelected = utils::ReadParam('choice', []); $aSelectedChoices[$index] = $aSelected; $this->oWizard->SetParameter('selected_components', json_encode($aSelectedChoices)); if ($this->GetStepInfo($index) == null) { throw new Exception('Internal error: invalid step "'.$index.'" for the choice of modules.'); } elseif ($bMoveForward) { if ($this->GetStepInfo(1 + $index) != null) { return new WizardState(WizStepModulesChoice::class, (1 + $index)); } else { // Exiting this step of the wizard, let's convert the selection into a list of modules $aModules = []; $aExtensions = []; $sDisplayChoices = ''; if (class_exists('CreateITILProfilesInstaller')) { $this->oWizard->SetParameter('old_addon', true); } [$aExtensionsAdded, $aExtensionsRemoved, $aExtensionsNotUninstallable] = $this->GetAddedAndRemovedExtensions($aExtensions); $this->oWizard->SetParameter('selected_modules', json_encode(array_keys($aModules))); $this->oWizard->SetParameter('selected_extensions', json_encode($aExtensions)); $this->oWizard->SetParameter('display_choices', $sDisplayChoices); $this->oWizard->SetParameter('extensions_added', json_encode($aExtensionsAdded)); $this->oWizard->SetParameter('removed_extensions', json_encode($aExtensionsRemoved)); $this->oWizard->SetParameter('extensions_not_uninstallable', json_encode(array_keys($aExtensionsNotUninstallable))); $sMode = $this->oWizard->GetParameter('mode', 'install'); if ($sMode == 'install' || !$this->IsDataAuditEnabled()) { return new WizardState(WizStepSummary::class); } else { return new WizardState(WizStepDataAudit::class); } } } } public function Display(WebPage $oPage) { $this->DisplayStep($oPage); } /** * @param \SetupPage $oPage * * @throws \Exception */ protected function DisplayStep($oPage) { // Sanity check (not stopper, to let developers go further...) if (! is_null($this->oMissingDependencyException)) { $oPage->warning($this->oMissingDependencyException->getHtmlDesc(), $this->oMissingDependencyException->getMessage()); } $this->bUpgrade = ($this->oWizard->GetParameter('install_mode') != 'install'); $aStepInfo = $this->GetStepInfo(); $oPage->add_style("div.choice { margin: 0.5em;}"); $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(".choice-disabled { color: #999; }"); $oPage->add_style("input.unremovable { accent-color: orangered;}"); $sManualInstallError = SetupUtils::CheckManualInstallDirEmpty( $this->aAnalyzeInstallationModules, $this->oWizard->GetParameter('extensions_dir', 'extensions') ); if ($sManualInstallError !== '') { $oPage->warning($sManualInstallError); } $oPage->add('
'); $sBannerPath = isset($aStepInfo['banner']) ? $aStepInfo['banner'] : ''; if (!empty($sBannerPath)) { if (substr($sBannerPath, 0, 1) == '/') { // absolute path, means relative to APPROOT $sBannerUrl = utils::GetDefaultUrlAppRoot(true).$sBannerPath; } else { // relative path: i.e. relative to the directory containing the XML file $sFullPath = dirname($this->GetSourceFilePath()).'/'.$sBannerPath; $sRealPath = realpath($sFullPath); $sBannerUrl = utils::GetDefaultUrlAppRoot(true).str_replace(realpath(APPROOT), '', $sRealPath); } $oPage->add(''); } $sDescription = $aStepInfo['description'] ?? ''; $oPage->add(''.$sDescription.''); $oPage->add('
'); // Build the default choices $aDefaults = $this->GetDefaults($aStepInfo, $this->aAnalyzeInstallationModules); $index = $this->GetStepIndex(); // retrieve the saved selection // use json_encode:decode to store a hash array: step_id => array(input_name => selected_input_id) $aParameters = json_decode($this->oWizard->GetParameter('selected_components', '{}'), true); if (!isset($aParameters[$index])) { $aParameters[$index] = $aDefaults; } $aSelectedComponents = $aParameters[$index]; $oPage->add('
'); $this->DisplayOptions($oPage, $aStepInfo, $aSelectedComponents, $aDefaults); $oPage->add('
'); $oPage->add_script( <<add_ready_script( <<bChoicesFromDatabase) { $this->GuessDefaultsFromModules($aInfo, $aDefaults, $aModules, $sParentId); } else { $this->GetDefaultsFromDatabase($aInfo, $aDefaults, $sParentId); } return $aDefaults; } protected function GetDefaultsFromDatabase($aInfo, &$aDefaults, $sParentId) { $aOptions = isset($aInfo['options']) ? $aInfo['options'] : []; foreach ($aOptions as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; if ($this->bUpgrade) { if ($this->oExtensionsMap->IsMarkedAsChosen($aChoice['extension_code'])) { $aDefaults[$sChoiceId] = $sChoiceId; } } elseif (isset($aChoice['default']) && $aChoice['default']) { $aDefaults[$sChoiceId] = $sChoiceId; } // Recurse for sub_options (if any) if (isset($aChoice['sub_options'])) { $this->GetDefaultsFromDatabase($aChoice['sub_options'], $aDefaults, $sChoiceId); } } $aAlternatives = $aInfo['alternatives'] ?? []; $sChoiceName = null; foreach ($aAlternatives as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; if ($sChoiceName == null) { $sChoiceName = $sChoiceId; // All radios share the same name } if ($this->bUpgrade) { if ($this->oExtensionsMap->IsMarkedAsChosen($aChoice['extension_code'])) { $aDefaults[$sChoiceName] = $sChoiceId; } } elseif (isset($aChoice['default']) && $aChoice['default']) { $aDefaults[$sChoiceName] = $sChoiceId; } // Recurse for sub_options (if any) if (isset($aChoice['sub_options'])) { $this->GetDefaultsFromDatabase($aChoice['sub_options'], $aDefaults, $sChoiceId); } } } /** * Try to guess the user choices based on the current list of installed modules... * @param array $aInfo * @param array $aDefaults * @param array $aModules * @param string $sParentId * @return array */ protected function GuessDefaultsFromModules($aInfo, &$aDefaults, $aModules, $sParentId = '') { $aRetScore = []; $aScores = []; $aOptions = isset($aInfo['options']) ? $aInfo['options'] : []; foreach ($aOptions as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; $aScores[$sChoiceId] = []; if (!$this->bUpgrade && isset($aChoice['default']) && $aChoice['default']) { $aDefaults[$sChoiceId] = $sChoiceId; } if ($this->bUpgrade) { // In upgrade mode, the defaults are the installed modules foreach ($aChoice['modules'] as $sModuleId) { if ($aModules[$sModuleId]['installed_version'] != '') { // A module corresponding to this choice is installed $aScores[$sChoiceId][$sModuleId] = true; } } // Used for migration from 1.3.x or before // Accept that the new version can have one new module than the previous version // The option is still selected $iSelected = count($aScores[$sChoiceId]); $iNeeded = count($aChoice['modules']); if (($iSelected > 0) && (($iNeeded - $iSelected) < 2)) { // All the modules are installed, this choice is selected $aDefaults[$sChoiceId] = $sChoiceId; } $aRetScore = array_merge($aRetScore, $aScores[$sChoiceId]); } if (isset($aChoice['sub_options'])) { $aScores[$sChoiceId] = array_merge($aScores[$sChoiceId], $this->GuessDefaultsFromModules($aChoice['sub_options'], $aDefaults, $sChoiceId)); } $index++; } $aAlternatives = isset($aInfo['alternatives']) ? $aInfo['alternatives'] : []; $sChoiceName = null; $sChoiceIdNone = null; foreach ($aAlternatives as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; $aScores[$sChoiceId] = []; if ($sChoiceName == null) { $sChoiceName = $sChoiceId; } if (!$this->bUpgrade && isset($aChoice['default']) && $aChoice['default']) { $aDefaults[$sChoiceName] = $sChoiceId; } if (isset($aChoice['sub_options'])) { // By default (i.e. install-mode), sub options can only be checked if the parent option is checked by default if ($this->bUpgrade || (isset($aChoice['default']) && $aChoice['default'])) { $aScores[$sChoiceId] = $this->GuessDefaultsFromModules($aChoice['sub_options'], $aDefaults, $aModules, $sChoiceId); } } $index++; } $iMaxScore = 0; if ($this->bUpgrade && (count($aAlternatives) > 0)) { // The installed choices have precedence over the 'default' choices // In case several choices share the same base modules, let's weight the alternative choices // based on their number of installed modules $sChoiceName = null; foreach ($aAlternatives as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; if ($sChoiceName == null) { $sChoiceName = $sChoiceId; } if (array_key_exists('modules', $aChoice)) { foreach ($aChoice['modules'] as $sModuleId) { if ($aModules[$sModuleId]['installed_version'] != '') { // A module corresponding to this choice is installed, increase the score of this choice if (!isset($aScores[$sChoiceId])) { $aScores[$sChoiceId] = []; } $aScores[$sChoiceId][$sModuleId] = true; $iMaxScore = max($iMaxScore, count($aScores[$sChoiceId])); } } //if (count($aScores[$sChoiceId]) == count($aChoice['modules'])) //{ // $iScore += 100; // Bonus for the parent when a choice is complete //} $aRetScore = array_merge($aRetScore, $aScores[$sChoiceId]); } $iMaxScore = max($iMaxScore, isset($aScores[$sChoiceId]) ? count($aScores[$sChoiceId]) : 0); } } if ($iMaxScore > 0) { $aNumericScores = []; foreach ($aScores as $sChoiceId => $aModules) { $aNumericScores[$sChoiceId] = count($aModules); } // The choice with the bigger score wins ! asort($aNumericScores, SORT_NUMERIC); $aKeys = array_keys($aNumericScores); $sBetterChoiceId = array_pop($aKeys); $aDefaults[$sChoiceName] = $sBetterChoiceId; } // echo "Scores:
".print_r($aScores, true)."

"; // echo "Defaults:
".print_r($aDefaults, true)."

"; return $aRetScore; } private function GetPhpExpressionEvaluator(): PhpExpressionEvaluator { if (!isset($this->oPhpExpressionEvaluator)) { $this->oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST); } return $this->oPhpExpressionEvaluator; } /** * Converts the list of selected "choices" into a list of "modules": take into account the selected and the mandatory modules * * @param array $aInfo Info about the "choice" array('options' => array(...), 'alternatives' => array(...)) * @param array $aSelectedChoices List of selected choices array('name' => 'selected_value_id') * @param array $aModules Return parameter: List of selected modules array('module_id' => true) * @param string $sParentId Used for recursion * * @return string A text representation of what will be installed */ protected function GetSelectedModules($aInfo, $aSelectedChoices, &$aModules, $sParentId = '', $sDisplayChoices = '', &$aSelectedExtensions = null) { if ($sParentId == '') { // Check once (before recursing) that the hidden modules are selected foreach ($this->aAnalyzeInstallationModules as $sModuleId => $aModule) { if (($sModuleId != ROOT_MODULE) && !isset($aModules[$sModuleId])) { if (($aModule['category'] == 'authentication') || (!$aModule['visible'] && !isset($aModule['auto_select']))) { $aModules[$sModuleId] = true; $sDisplayChoices .= '
  • '.$aModule['label'].' (hidden)
  • '; } } } } $aOptions = isset($aInfo['options']) ? $aInfo['options'] : []; foreach ($aOptions as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; $aModuleInfo = []; // Get the extension corresponding to the choice foreach ($this->oExtensionsMap->GetAllExtensions() as $sExtensionVersion => $oExtension) { if (utils::StartsWith($sExtensionVersion, $aChoice['extension_code'].'/')) { $aModuleInfo = $oExtension->aModuleInfo; break; } } if ((isset($aChoice['mandatory']) && $aChoice['mandatory']) || (isset($aSelectedChoices[$sChoiceId]) && ($aSelectedChoices[$sChoiceId] == $sChoiceId))) { $sDisplayChoices .= '
  • '.$aChoice['title'].'
  • '; if (isset($aChoice['modules'])) { foreach ($aChoice['modules'] as $sModuleId) { $bSelected = true; if (isset($aModuleInfo[$sModuleId])) { // Test if module has 'auto_select' $aInfo = $aModuleInfo[$sModuleId]; if (isset($aInfo['auto_select'])) { // Check the module selection try { SetupInfo::SetSelectedModules($aModules); $bSelected = $this->GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($aInfo['auto_select']); } catch (ModuleFileReaderException $e) { //logged already $bSelected = false; } } } if ($bSelected) { $aModules[$sModuleId] = true; // store the Id of the selected module SetupInfo::SetSelectedModules($aModules); } } } $sChoiceType = isset($aChoice['type']) ? $aChoice['type'] : 'wizard_option'; if ($aSelectedExtensions !== null) { $aSelectedExtensions[] = $aChoice['extension_code']; } // Recurse only for selected choices if (isset($aChoice['sub_options'])) { $sDisplayChoices .= ''; } $sDisplayChoices .= ''; } } $aAlternatives = isset($aInfo['alternatives']) ? $aInfo['alternatives'] : []; $sChoiceName = null; foreach ($aAlternatives as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; if ($sChoiceName == null) { $sChoiceName = $sChoiceId; } if ((isset($aChoice['mandatory']) && $aChoice['mandatory']) || (isset($aSelectedChoices[$sChoiceName]) && ($aSelectedChoices[$sChoiceName] == $sChoiceId))) { $sDisplayChoices .= '
  • '.$aChoice['title'].'
  • '; if ($aSelectedExtensions !== null) { $aSelectedExtensions[] = $aChoice['extension_code']; } if (isset($aChoice['modules'])) { foreach ($aChoice['modules'] as $sModuleId) { $aModules[$sModuleId] = true; // store the Id of the selected module } } // Recurse only for selected choices if (isset($aChoice['sub_options'])) { $sDisplayChoices .= ''; } $sDisplayChoices .= ''; } } if ($sParentId == '') { // Last pass (after all the user's choices are turned into "selected" modules): // Process 'auto_select' modules for modules that are not already selected do { // Loop while new modules are added... $bModuleAdded = false; foreach ($this->aAnalyzeInstallationModules as $sModuleId => $aModule) { if (($sModuleId != ROOT_MODULE) && !array_key_exists($sModuleId, $aModules) && isset($aModule['auto_select'])) { try { SetupInfo::SetSelectedModules($aModules); $bSelected = $this->GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($aModule['auto_select']); if ($bSelected) { $aModules[$sModuleId] = true; // store the Id of the selected module $sDisplayChoices .= '
  • '.$aModule['label'].' (auto_select)
  • '; $bModuleAdded = true; } } catch (ModuleFileReaderException $e) { //logged already $sDisplayChoices .= '
  • Warning: auto_select failed with exception ('.$e->getMessage().') for module "'.$sModuleId.'"
  • '; } } } } while ($bModuleAdded); } return $sDisplayChoices; } protected function GetStepIndex() { switch ($this->sCurrentState) { case 'start_install': case 'start_upgrade': $index = 0; break; default: $index = (int)$this->sCurrentState; } return $index; } protected function GetStepInfo($idx = null) { $index = $idx ?? $this->GetStepIndex(); if (is_null($this->aSteps)) { $this->oWizard->SetParameter('additional_extensions_modules', json_encode([])); // Default value, no additional extensions if (@file_exists($this->GetSourceFilePath())) { // Found an "installation.xml" file, let's use this definition for the wizard $aParams = new XMLParameters($this->GetSourceFilePath()); $this->aSteps = $aParams->Get('steps', []); if ($index + 1 >= count($this->aSteps)) { //make sure we also cache next step as well $aOptions = $this->oExtensionsMap->GetAllExtensionsOptionInfo(); // Display this step of the wizard only if there is something to display if (count($aOptions) > 0) { $this->aSteps[] = [ 'title' => 'Extensions', 'description' => '

    Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.

    ', 'banner' => '/images/icons/icons8-puzzle.svg', 'options' => $aOptions, ]; $this->oWizard->SetParameter('additional_extensions_modules', json_encode($aOptions)); } } } else { $aOptions = $this->oExtensionsMap->GetAllExtensionsOptionInfo(); // 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. $this->aSteps = [ [ 'title' => 'Modules Selection', 'description' => '

    Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.

    ', 'banner' => '/images/icons/icons8-apps-tab.svg', 'options' => $aOptions, ], ]; } } return $this->aSteps[$index] ?? null; } public function ComputeChoiceFlags(array $aChoice, string $sChoiceId, array $aSelectedComponents, bool $bAllDisabled, bool $bDisableUninstallCheck, bool $bUpgradeMode) { $oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']); //If the extension is missing from disk, it won't exist in the ExtensionsMap, thus returning null $bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] === true || $aChoice['uninstallable'] === 'yes' : $oITopExtension->CanBeUninstalled(); $bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId); $bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true; $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']); $bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled; $bChecked = $bSelected; $bDisabled = false; if ($bMissingFromDisk) { $bDisabled = true; $bChecked = false; } elseif ($bMandatory || $bInstalled && !$bCanBeUninstalled) { $bDisabled = true; $bChecked = true; } if ($bAllDisabled) { $bDisabled = true; } 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 = $aStepInfo['options'] ?? []; $aAlternatives = $aStepInfo['alternatives'] ?? []; $bDisableUninstallCheck = (bool)$this->oWizard->GetParameter('force-uninstall', false); foreach ($aOptions as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; $sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"'; $sId = utils::EscapeHtml($aChoice['extension_code']); $aFlags = $this->ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade); $sTooltip = ''; $sUnremovable = ''; if ($aFlags['missing']) { $sTooltip .= '
    source removed
    '; } if ($aFlags['installed']) { $sTooltip .= '
    installed
    '; $sTooltip .= '
    to be uninstalled
    '; } else { $sTooltip .= '
    to be installed
    '; $sTooltip .= '
    not installed
    '; } if (!$aFlags['uninstallable']) { $sTooltip .= '
    cannot be uninstalled
    '; } if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) { $this->bCanMoveForward = false;//Disable "Next" } $sChecked = $aFlags['checked'] ? ' checked ' : ''; $sDisabled = $aFlags['disabled'] ? ' disabled data-disabled="disabled" ' : ''; $sMissingModule = $aFlags['missing'] ? 'setup-extension--missing' : ''; $sHiddenInput = $aFlags['disabled'] && $aFlags['checked'] ? '' : ''; $oPage->add('
    '.$sHiddenInput.' '); $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $aFlags['disabled'], $sTooltip); $oPage->add('
    '); } $sChoiceName = null; $sDisabled = ''; $bDisabled = false; $sChoiceIdNone = null; foreach ($aAlternatives as $index => $aChoice) { $sChoiceId = $sParentId.self::$SEP.$index; if ($sChoiceName == null) { $sChoiceName = $sChoiceId; // All radios share the same name } $bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId); $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault); if ($bMandatory || $bAllDisabled) { // One choice is mandatory, all alternatives are disabled $sDisabled = ' disabled data-disabled="disabled"'; $bDisabled = true; } if ((!isset($aChoice['sub_options']) || (count($aChoice['sub_options']) == 0)) && (!isset($aChoice['modules']) || (count($aChoice['modules']) == 0))) { $sChoiceIdNone = $sChoiceId; // the "None" / empty choice } } if (!array_key_exists($sChoiceName, $aDefaults) || ($aDefaults[$sChoiceName] == $sChoiceIdNone)) { // The "none" choice does not disable the selection !! $sDisabled = ''; $bDisabled = false; } foreach ($aAlternatives as $index => $aChoice) { $sAttributes = ''; $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 } $bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId); $bSelected = isset($aSelectedComponents[$sChoiceName]) && ($aSelectedComponents[$sChoiceName] == $sChoiceId); if (!isset($aSelectedComponents[$sChoiceName]) && ($sChoiceIdNone != null)) { // No choice selected, select the "None" option $bSelected = ($sChoiceId == $sChoiceIdNone); } $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault); if ($bSelected) { $sAttributes = ' checked '; } $sHidden = ''; if ($bMandatory && $bDisabled) { $sAttributes = ' checked '; $sHidden = ''; } $oPage->add('
    '.$sHidden.' '); $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled && !$bSelected); $oPage->add('
    '); } } protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $sTooltip = '') { $sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? 'More information' : ''; $sSourceLabel = $aChoice['source_label'] ?? ''; $sId = utils::EscapeHtml($aChoice['extension_code']); $oPage->add(' '.$sMoreInfo.''); $sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : ''; $oPage->add('
    '.$sDescription.''); if (isset($aChoice['sub_options'])) { $this->DisplayOptions($oPage, $aChoice['sub_options'], $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled); } $oPage->add('
    '); } protected function GetSourceFilePath() { $sSourceDir = $this->oWizard->GetParameter('source_dir'); return $sSourceDir.'/installation.xml'; } public function CanMoveForward() { return true; } public function JSCanMoveForward() { return $this->bCanMoveForward ? 'return true;' : 'return false;'; } public function GetNextButtonLabel() { if (!$this->bCanMoveForward) { return 'Non-uninstallable extension missing'; } if ($this->GetStepInfo(1 + $this->GetStepIndex()) === null && $this->IsDataAuditEnabled()) { return 'Check compatibility'; } return 'Next'; } }