');
$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 .= '
';
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 .= '
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 .= '