Compare commits

...

14 Commits

Author SHA1 Message Date
Timmy38
e0ef950664 N°9906 Add tests 2026-09-16 10:44:32 +02:00
Timmy38
8bdb7dd03a N°9906 Fix selected flag 2026-09-15 10:44:55 +02:00
Timmy38
f1d0432220 N°9906 Code clean up 2026-09-15 10:40:44 +02:00
Timmy38
34d26cde6c N°9906 Code clean up 2026-09-15 10:40:43 +02:00
Timmy38
0c4f208ca8 N°9906 Updating phpunit tests 2026-09-15 10:40:43 +02:00
Timmy38
538cf46d5f N°9906 Fix missing $ and phpunit tests 2026-09-15 10:40:43 +02:00
Timmy38
f9b66e88ec N°9906 Fix CS 2026-09-15 10:40:42 +02:00
Timmy38
a81c9a0be4 N°9906 Warn user that an extension is part of package 2026-09-15 10:40:42 +02:00
Molkobain
4e1bf80b9f Merge remote-tracking branch 'origin/support/3.2' into develop 2026-09-15 10:17:15 +02:00
Molkobain
2ac4229ee0 N°10070 - Loss of DroidSansFallback font which impacts exports in east asian character sets (#1048)
* N°10070 - Loss of DroidSansFallback font which impacts exports in east asian character sets

* N°10070 - Add unit test
2026-09-15 10:03:52 +02:00
Timmy38
e6d485855e N°10045 Fix modules in production-modules automatically set to visible false 2026-09-14 16:34:40 +02:00
Stephen Abello
ab9d270b62 Merge branch 'support/3.2' into develop 2026-09-14 09:54:17 +02:00
Stephen Abello
4a6af15237 N°10041 - Fullscreen CKeditor in a modal can't take the focus (#1043) 2026-09-14 09:52:21 +02:00
jf-cbd
05e585ced3 📝 Update enhancement.yml and pull_request_template.md 2026-09-11 16:57:45 +02:00
37 changed files with 3418 additions and 49 deletions

View File

@@ -9,6 +9,7 @@ body:
- Please describe what's your improvement proposition.
- Then tell us if you're willing to create a PR for this enhancement ? If so, we'll indicate in the issue if we're interested in it.
- The more users are affected and the greater is the impact, the more likely this improvement will be accepted. If you identified a bug and have an improvement proposition, please use the 'Bug report' template instead."
Please note we're not willing to accept code improvement (e.g. refactoring) that won't have a "functional" change for the user.
- type: textarea
id: enhancement_details

View File

@@ -2,8 +2,11 @@
IMPORTANT: Before creating your PR, please create an issue first to know if Combodo is interested in your contribution (not needed for translations PR).
Since we may refuse a PR, it's preferable to create an issue first, to avoid spending time coding something that won't be accepted.
PR that will benefit the most users and has a greater impact on the product are more likely to be accepted, so please explain your use case and why you think it would be useful for the community.
We're not willing to receive PR concerning codes improvements (e.g. code refactoring), except if it's part of a code that brings/corrects a feature for users.
Please note that we're working with sprints, so we reserve the right to decline a PR if we do not plan to work on that subject in the 6 coming months.
Thank you for your comprehension 😊
Once you've done it, and we confirmed we're interested in it, please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
Once you've created the issue, and we confirmed we're interested in it, please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
Any PRs not following the guidelines or with missing information will not be considered.
-->

View File

@@ -61,7 +61,7 @@ foreach ($aTcpdfFontsDirContent as $sTcpdfFontResourceName) {
* 2) Then adding the DroidSansFallback font (useful for CJK data for example)
*/
echo $sCurrentScriptFileName.": ---2) Copying font files to TCPDF ($sTcPdfFontsFolder)...\n";
$aFontFilesToCopy = glob(__DIR__.'\droidsansfallback.*');
$aFontFilesToCopy = glob(__DIR__.DIRECTORY_SEPARATOR.'droidsansfallback.*');
foreach ($aFontFilesToCopy as $sFontFileToCopy) {
$sFontFileName = basename($sFontFileToCopy);
echo $sCurrentScriptFileName.': copying '.$sFontFileName."\n";

View File

@@ -38,6 +38,7 @@ class DataFeatureRemovalController extends Controller
private array $aCountClassesToCleanup = [];
private array $aAnalysisDataTable = [];
private array $aDeletionExecutionSummary = [];
private ?array $aBasePackageModules = null;
private int $iCount = 0;
private int $iColumnCount = 2;
@@ -371,18 +372,23 @@ class DataFeatureRemovalController extends Controller
foreach ($aExtensionsRef as $oExtension) {
/** @var \iTopExtension $oExtension */
$aMetaData = [$oExtension->sVersion, $oExtension->GetExtensionSourceLabel(), $oExtension->sCode];
$bIsPackageExtension = $this->IsIncludedInPackage($oExtension);
$aExtensionsData[$oExtension->sCode] = [
'version' => $oExtension->sVersion,
'label' => $oExtension->sLabel,
'code' => $oExtension->sCode,
'description' => $oExtension->sDescription,
'source' => $oExtension->GetExtensionSourceLabel(),
'metadata' => $aMetaData,
'installed' => $oExtension->bInstalled,
'extra_flags' => [
'uninstallable' => $oExtension->CanBeUninstalled(),
'remote' => $oExtension->IsRemote(),
'missing' => $oExtension->bRemovedFromDisk,
'dependency_issue' => $oExtension->HasDependencyIssue(),
'already_part_of_itop' => $bIsPackageExtension,
],
];
@@ -391,6 +397,52 @@ class DataFeatureRemovalController extends Controller
return $aExtensionsData;
}
/**
* Returns true when all modules of a non-package extension are already included in base package modules.
*/
public function IsIncludedInPackage(iTopExtension $oExtension): bool
{
if ($oExtension->sSource === iTopExtension::SOURCE_WIZARD) {
return false;
}
$aModules = $oExtension->aModules ?? [];
if (!is_array($aModules) || empty($aModules)) {
return false;
}
$aBasePackageModules = $this->GetBasePackageModules();
foreach ($aModules as $sModuleId) {
if (!in_array($sModuleId, $aBasePackageModules)) {
return false;
}
}
return true;
}
public function GetBasePackageModules(): array
{
if ($this->aBasePackageModules !== null) {
return $this->aBasePackageModules;
}
$oConfig = MetaModel::GetConfig();
$oExtensionsMap = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap();
$aSelectedExtensions = $oExtensionsMap->GetSelectedExtensions($oConfig);
foreach ($aSelectedExtensions as $index => $sExtensionCode) {
$oExtension = $oExtensionsMap->GetFromExtensionCode($sExtensionCode);
if ($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) {
unset($aSelectedExtensions[$index]);
}
}
$sSourceEnv = MetaModel::GetEnvironment();
$oRuntimeEnvironment = new RunTimeEnvironment($sSourceEnv, false);
$this->aBasePackageModules = $oRuntimeEnvironment->GetModulesToLoadFromSelectedExtensions($oConfig, $aSelectedExtensions);
return $this->aBasePackageModules;
}
private function GetExtensionsDiff(array $aAddedExtensions, array $aRemovedExtensions): array
{
$aExtensions = [];

View File

@@ -32,9 +32,9 @@
{% UIColumn Standard {} %}
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
{% if aExtension['installed'] %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : aExtension['metadata'], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% else %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : aExtension['metadata'], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% endif %}
{% endfor %}
{% EndUIColumn %}

View File

@@ -12,9 +12,9 @@
{% UIColumn Standard {} %}
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
{% if aExtension['installed'] %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : aExtension['metadata'], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% else %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : aExtension['metadata'], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% endif %}
{% endfor %}
{% EndUIColumn %}

View File

@@ -23,6 +23,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'Diese Extension wird während des Setups deinstalliert.',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'nicht deinstallierbar',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Sobald diese Extension installiert ist, sollte sie nicht mehr deinstalliert werden.',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'auf dem Datenträger nicht vorhanden',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'Das lokale Verzeichnis der Extension wurde vom Datenträger entfernt. Dadurch wird die Deinstallation dieser Extension erzwungen.',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'Über %1$s',

View File

@@ -21,6 +21,8 @@ Dict::Add('EN US', 'English', 'English', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s',

View File

@@ -23,6 +23,8 @@ Dict::Add('EN GB', 'British English', 'British English', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('FR FR', 'French', 'Français', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'Cette extension sera désinstallée lors de l\'installation.',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'non désinstallable',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Une fois cette extension installée, elle ne devrait pas être désinstallée.',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'déjà dans iTop',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'Tous les modules de cette extension font déjà partie du package iTop. Pour désinstaller cette extension, vous devez supprimer son dossier.',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'supprimée du disque',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'Le dossier local de l\'extension a été supprimé du disque. Cela forcera la désinstallation de cette extension.',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'À propos de %1$s',

View File

@@ -23,6 +23,8 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'Это расширение будет удалено в ходе установки.',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'нельзя удалить',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'После установки это расширение не должно удаляться.',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'отсутствует на диске',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'Локальная папка расширения удалена с диска. Это приведёт к принудительному удалению расширения.',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'О расширении %1$s',

View File

@@ -23,6 +23,8 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -23,6 +23,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop' => 'already part of iTop~~',
'UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+' => 'All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder.~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',

View File

@@ -0,0 +1,19 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
// Overload of the default dialog widget
$.widget('ui.dialog', $.ui.dialog, {
_allowInteraction: function (oEvent) {
const oTarget = $(oEvent.target);
// If we interact with a CKEditor instance in fullscreen mode, we need to allow it
// We could check if the current instance is in the dialog, but it's easier to always allow it in fullscreen
if (oTarget.closest('.ck.ck-fullscreen__main-wrapper, .ck-body-wrapper').length > 0) {
return true;
}
// If that's not a specific case, fall back to the default behavior
return this._super(oEvent);
}
});

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -753,7 +753,7 @@ class iTopExtensionsMap
* @param string[] $aRemovedExtensions
* @return string[] :
*/
public function GetSelectedExtensions(Config $oConfig, array $aAddedExtensions, array $aRemovedExtensions): array
public function GetSelectedExtensions(Config $oConfig, array $aAddedExtensions = [], array $aRemovedExtensions = []): array
{
$aDbChoices = $this->GetChoicesFromDatabase($oConfig);
foreach ($aDbChoices as $i => $sChoice) {

View File

@@ -1670,20 +1670,7 @@ JS
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV));
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensionCodes);
$aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $aDirsToScan, $bAbortOnMissingDependency, $aModulesToLoad);
$bRemoteExtensionsShouldBeMandatory = !$oWizard->GetParameter('force-uninstall', false);
if ($bRemoteExtensionsShouldBeMandatory) {
foreach ($aAvailableModules as $key => $aModule) {
$bIsExtra = (array_key_exists('root_dir', $aModule) && (strpos($aModule['root_dir'], $sExtraDir) !== false)); // Some modules (root, datamodel) have no 'root_dir'
if ($bIsExtra) {
// Modules in data/production-modules/ are considered as mandatory and always installed
$aAvailableModules[$key]['visible'] = false;
}
}
}
return $aAvailableModules;
return $oProductionEnv->AnalyzeInstallation($oConfig, $aDirsToScan, $bAbortOnMissingDependency, $aModulesToLoad);
}
/**

View File

@@ -169,6 +169,36 @@ class WizStepModulesChoice extends AbstractWizStepInstall
return new WizardState(WizStepModulesChoice::class, (string)($index - 1));
}
protected ?array $aSelectedModules = null;
public function GetAllSelectedModulesUntilNow(): array
{
if (!is_null($this->aSelectedModules)) {
return $this->aSelectedModules;
}
$this->aSelectedModules = [];
$aSelectedChoices = json_decode($this->oWizard->GetParameter('selected_components', '{}'), true);
$iNextStep = $this->GetStepIndex();
$index = $iNextStep;
while (isset($aSelectedChoices[$iNextStep])) {
//Let's empty the next steps, we only want what has been chosen before
$aSelectedChoices[$iNextStep] = [];
$iNextStep++;
}
$aPreviousSteps = [];
for ($i = 0; $i < $index; $i++) {
$aStepInfo = $this->GetStepInfo($i);
$this->GetSelectedModules($aStepInfo, $aSelectedChoices[$i], $this->aSelectedModules);
$aPreviousSteps = array_merge($aPreviousSteps, $this->aSelectedModules);
}
return $this->aSelectedModules;
}
public function GetWizardSteps(): array
{
$aSteps = [
@@ -761,6 +791,22 @@ EOF
return $this->aSteps[$index] ?? null;
}
public function ExtensionIsAlreadyIncludedInPreviousChoices(?iTopExtension $oITopExtension): bool
{
if (is_null($oITopExtension) || empty($oITopExtension->aModules)) {
return false;
}
$aAllPreviousChoicesModules = $this->GetAllSelectedModulesUntilNow();
foreach ($oITopExtension->aModules as $sModuleId) {
if (!isset($aAllPreviousChoicesModules[$sModuleId])) {
return false;
}
}
return true;
}
public function ComputeChoiceFlags(array $aChoice, string $sChoiceId, array $aSelectedComponents, bool $bAllDisabled, bool $bDisableUninstallCheck): array
{
if (array_key_exists($sChoiceId, $this->aFlagsByChoiceId)) {
@@ -777,6 +823,7 @@ EOF
$bDependencyIssue = $oITopExtension?->HasDependencyIssue() ?? false;
$bIsRemoteExtension = $oITopExtension?->sSource === iTopExtension::SOURCE_REMOTE;
$bIsPackageExtension = $oITopExtension?->sSource === iTopExtension::SOURCE_WIZARD;
$bAlreadyIncluded = $this->ExtensionIsAlreadyIncludedInPreviousChoices($oITopExtension);
$bDoNotUninstall = !$bCanBeUninstalled || $bIsRemoteExtension;
$bChecked = $bSelected;
@@ -788,6 +835,9 @@ EOF
} elseif ($bMandatory && $bIsPackageExtension) {
$bDisabled = true;
$bChecked = true;
} elseif ($bAlreadyIncluded) {
$bDisabled = true;
$bChecked = true;
} else {
if ($bDependencyIssue) {
// If the extension has a dependency issue, it cannot be checked and must be unchecked using the "force-uninstall" option
@@ -823,6 +873,7 @@ EOF
$aFlags = [
'uninstallable' => $bCanBeUninstalled,
'already_included' => $bAlreadyIncluded,
'dependency_issue' => $bDependencyIssue,
'mandatory' => $bMandatory,
'missing' => $bMissingFromDisk,
@@ -922,7 +973,7 @@ EOF
<a class="setup--wizard-choice--more-info" target="_blank" href="'.$aChoice['more_info'].'">
<i class="setup-extension--icon fas fa-external-link-alt" title="More information"></i>
</a>' : '';
$sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : '';
$sDescription = isset($aChoice['description']) ? trim(utils::EscapeHtml($aChoice['description'])) : '';
$sId = utils::EscapeHtml($aChoice['extension_code']);
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
$sDisabled = $aFlags['disabled'] ? ' disabled data-disabled="disabled"' : '';
@@ -947,11 +998,15 @@ EOF
if ($aFlags['dependency_issue']) {
$sTooltip .= '<div id="badge--'.$sId.'--cannot-be-installed" class="ibo-badge ibo-block ibo-is-orange" title="This extension cannot be installed because one or more dependencies are not satisfied." >cannot be installed</div>';
}
if ($aFlags['already_included']) {
$sTooltip .= '<div id="badge--'.$sId.'--already-part-of-itop" class="ibo-badge ibo-block ibo-is-yellow" title="All modules in this extension are already part of the iTop package. To uninstall this extension, you have to delete its folder." >already part of iTop</div>';
}
$sMetadata = '';
if (isset($aChoice['version']) && isset($aChoice['source_label'])) {
$sMetadata = '<span>v'.$aChoice['version'].'</span><span>'.$aChoice['source_label'].'</span><span>'.$aChoice['extension_code'].'</span>';
}
$sChoiceDisabled = $aFlags['disabled'] && !$aFlags['checked'] ? 'choice-disabled' : '';
$oPage->add('

View File

@@ -18,6 +18,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
private const BADGE_ID_TO_BE_INSTALLED = 'to-be-installed';
private const BADGE_ID_TO_BE_UNINSTALLED = 'to-be-uninstalled';
private const BADGE_ID_NOT_UNINSTALLABLE = 'not-uninstallable';
private const BADGE_ID_ALREADY_PART_OF_ITOP = 'already-part-of-itop';
private const BADGE_ID_MISSING_FROM_DISK = 'missing-from-disk';
private const BADGE_ID_CANNOT_BE_INSTALLED = 'cannot-be-installed';
@@ -27,10 +28,11 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
$bUninstallable = $aExtraFlags['uninstallable'] ?? true;
$bMissingFromDisk = $aExtraFlags['missing'] ?? false;
$bDependencyIssue = $aExtraFlags['dependency_issue'] ?? false;
$bAlreadyPartOfITop = $aExtraFlags['already_part_of_itop'] ?? false;
$bSelected = $aExtraFlags['selected'] ?? true;
$bDisabled = $aExtraFlags['disabled'] ?? false;
$bRemote = $aExtraFlags['remote'] ?? false;
self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk, $bDependencyIssue, $sCode);
self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk, $bDependencyIssue, $bAlreadyPartOfITop, $sCode);
$oBadgeInstalled = BadgeUIBlockFactory::MakeGreen(
Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled'),
Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled+'),
@@ -51,6 +53,10 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
if ($bMissingFromDisk || $bDependencyIssue) {
$oExtensionDetails->GetToggler()->SetIsToggled(false);
$oExtensionDetails->GetToggler()->SetIsDisabled(true);
} elseif ($bAlreadyPartOfITop) {
$bSelected = true;
$oExtensionDetails->GetToggler()->SetIsToggled(true);
$oExtensionDetails->GetToggler()->SetIsDisabled(true);
} elseif ((!$bUninstallable || $bRemote) && !$bDisabled) {
$oExtensionDetails->AllowForceUninstall();
$oExtensionDetails->GetToggler()->SetIsDisabled(true);
@@ -72,9 +78,10 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
$aBadges = [];
$bUninstallable = $aExtraFlags['uninstallable'] ?? true;
$bDependencyIssue = $aExtraFlags['dependency_issue'] ?? false;
$bAlreadyPartOfITop = $aExtraFlags['already_part_of_itop'] ?? false;
$bSelected = $aExtraFlags['selected'] ?? false;
$bDisabled = $aExtraFlags['disabled'] ?? false;
self::AddExtraBadges($aBadges, $bUninstallable, false, $bDependencyIssue, $sCode);
self::AddExtraBadges($aBadges, $bUninstallable, false, $bDependencyIssue, $bAlreadyPartOfITop, $sCode);
$oBadgeInstalled = BadgeUIBlockFactory::MakeGrey(
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled'),
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled+'),
@@ -94,6 +101,9 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
if ($bDependencyIssue) {
$oExtensionDetails->GetToggler()->SetIsToggled(false);
$oExtensionDetails->GetToggler()->SetIsDisabled(true);
} elseif ($bAlreadyPartOfITop) {
$oExtensionDetails->GetToggler()->SetIsToggled(true);
$oExtensionDetails->GetToggler()->SetIsDisabled(true);
}
if ($bSelected) {
@@ -107,7 +117,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
return $oExtensionDetails;
}
private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk, bool $bDependencyIssue, string $sCode)
private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk, bool $bDependencyIssue, bool $bAlreadyPartOfITop, string $sCode)
{
if (!$bUninstallable) {
$aBadges[] = BadgeUIBlockFactory::MakeYellow(
@@ -130,6 +140,13 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
self::GetBadgeId($sCode, self::BADGE_ID_CANNOT_BE_INSTALLED)
);
}
if ($bAlreadyPartOfITop) {
$aBadges[] = BadgeUIBlockFactory::MakeYellow(
Dict::S('UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop'),
Dict::S('UI:Layout:ExtensionsDetails:BadgeAlreadyPartOfITop+'),
self::GetBadgeId($sCode, self::BADGE_ID_ALREADY_PART_OF_ITOP)
);
}
}
/**

View File

@@ -209,6 +209,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->LinkScriptFromAppRoot('js/pages/backoffice/keyboard-shortcuts.js');
// Used throughout the app.
$this->LinkScriptFromAppRoot('js/pages/backoffice/dialog.js');
$this->LinkScriptFromAppRoot('js/pages/backoffice/toolbox.js');
$this->LinkScriptFromAppRoot('js/pages/backoffice/on-ready.js');

View File

@@ -0,0 +1,38 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\DotMake\Dependencies\Composer\Tcpdf;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
/**
* @coversNothing
*/
class TcpdfUpdateFontsTest extends ItopTestCase
{
public function testThatDroidSansFallbackFilesAreCopiedToTcpdfFontsFolderAfterLibraryUpdate(): void
{
$sSourcePattern = APPROOT
.'.make'.DIRECTORY_SEPARATOR.'dependencies'.DIRECTORY_SEPARATOR.'composer'.DIRECTORY_SEPARATOR.'tcpdf'.DIRECTORY_SEPARATOR.'droidsansfallback.*';
$aSourceFiles = glob($sSourcePattern);
$this->assertIsArray($aSourceFiles, 'Unable to read source TCPDF custom font files.');
$this->assertNotEmpty($aSourceFiles, 'No source files found for pattern droidsansfallback.*');
foreach ($aSourceFiles as $sSourceFilePath) {
$sFontFileName = basename($sSourceFilePath);
$sDestinationFilePath = APPROOT
.'lib'.DIRECTORY_SEPARATOR.'tecnickcom'.DIRECTORY_SEPARATOR.'tcpdf'.DIRECTORY_SEPARATOR.'fonts'.DIRECTORY_SEPARATOR.$sFontFileName;
$this->assertFileExists($sDestinationFilePath, "Missing copied font file: {$sFontFileName}");
$this->assertSame(
hash_file('sha256', $sSourceFilePath),
hash_file('sha256', $sDestinationFilePath),
"Copied font file content mismatch: {$sFontFileName}"
);
}
}
}

View File

@@ -9,6 +9,7 @@ namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval\Service;
use Combodo\iTop\DataFeatureRemoval\Controller\DataFeatureRemovalController;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use iTopExtension;
/**
* @see DataFeatureController
@@ -32,4 +33,49 @@ class DataFeatureControllerTest extends ItopDataTestCase
$expected = '{"0":"itop-container-mgmt","1":"combodo-monitoring"}';
$this->assertEquals($expected, $this->InvokeNonPublicMethod(DataFeatureRemovalController::class, 'ConvertIntoSetupFormat', $oController, [ $aExtensions]));
}
public function testGetBasePackageModules()
{
$oController = new DataFeatureRemovalController();
$aModules = $oController->GetBasePackageModules();
$this->assertNotEmpty($aModules);
$this->assertContains('itop-structure', $aModules);
$this->assertContains('authent-local', $aModules);
$this->assertContains('itop-backup', $aModules);
$this->assertContains('itop-tickets', $aModules);
}
public function testIsIncludedInPackage()
{
$oController = $this->getMockBuilder(DataFeatureRemovalController::class)
->onlyMethods(['GetBasePackageModules'])
->getMock();
$oController->method('GetBasePackageModules')->willReturn([
'itop-structure',
'authent-local',
'itop-backup',
'itop-tickets',
]);
$oIncludedExtension = new iTopExtension();
$oIncludedExtension->sSource = iTopExtension::SOURCE_MANUAL;
$oIncludedExtension->aModules = ['itop-structure', 'authent-local'];
$this->assertTrue($oController->IsIncludedInPackage($oIncludedExtension), 'IsIncludedInPackage should return true if all modules are included in the base package');
$oNotIncludedExtension = new iTopExtension();
$oNotIncludedExtension->sSource = iTopExtension::SOURCE_MANUAL;
$oNotIncludedExtension->aModules = ['itop-structure', 'combodo-non-existing-module'];
$this->assertFalse($oController->IsIncludedInPackage($oNotIncludedExtension), 'IsIncludedInPackage should return false if at least one of its modules is not included in the base package');
$oEmptyExtension = new iTopExtension();
$oEmptyExtension->sSource = iTopExtension::SOURCE_MANUAL;
$oEmptyExtension->aModules = [];
$this->assertFalse($oController->IsIncludedInPackage($oEmptyExtension), 'IsIncludedInPackage should return false if the extension has no modules');
$oPackageExtension = new iTopExtension();
$oPackageExtension->sSource = iTopExtension::SOURCE_WIZARD;
$oPackageExtension->aModules = ['itop-structure', 'authent-local'];
$this->assertFalse($oController->IsIncludedInPackage($oPackageExtension), 'IsIncludedInPackage should return false if the extension is a package extension');
}
}

View File

@@ -2,9 +2,13 @@
namespace Combodo\iTop\Test\UnitTest\Setup;
use AnalyzeInstallation;
use CheckResult;
use Combodo\iTop\Setup\FeatureRemoval\ModelReflectionSerializer;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use Config;
use ModuleDiscovery;
use WizardController;
use SetupUtils;
/**
@@ -28,6 +32,13 @@ class SetupUtilsTest extends ItopTestCase
$this->RequireOnceItopFile('setup/setuputils.class.inc.php');
$this->RequireOnceItopFile('setup/setuppage.class.inc.php');
$this->RequireOnceItopFile('setup/wizardcontroller.class.inc.php');
}
protected function tearDown(): void
{
parent::tearDown();
ModuleDiscovery::ResetCache();
}
/**
@@ -219,6 +230,30 @@ OUTPUT;
$this->ValidateCheckResults($expected, $aRes);
}
/**
* Bug N°10045
*/
public function testAnalyzeInstallationDoesNotAutomaticallySetModulesInProductionModulesVisibilityToFalse()
{
$this->RequireOnceItopFile('setup/moduleinstallation/AnalyzeInstallation.php');
$this->SetNonPublicProperty(AnalyzeInstallation::GetInstance(), 'aAvailableModules', null);
$sRemoteEnv = 'production-temp';
$sExtraDir = \utils::GetDataPath().$sRemoteEnv.'-modules/';
$sExtraModuleName = 'extra-module';
$this->createModule($sExtraDir, $sExtraModuleName, '1.0.0');
$oWizard = new WizardController('WizStepWelcome');
$oWizard->SetParameter('source_dir', APPROOT.'datamodels/2.x');
$oWizard->SetParameter('remote_env', $sRemoteEnv);
$aModules = SetupUtils::AnalyzeInstallation($oWizard);
$this->assertContains($sExtraModuleName, array_keys($aModules), 'Module discovery should have found the extra module');
$this->assertTrue($aModules[$sExtraModuleName]['visible'], 'AnalyzeInstallation should not have automatically set the extra module visibility to false');
}
private function ValidateCheckResults(array $expected, array $aActualCheckResults)
{
$aActual = [];
@@ -232,4 +267,37 @@ OUTPUT;
self::assertEquals($expected, $aActual);
}
protected function createModule($sDirectory, $sModuleName, $sModuleVersion, $bMandatory = false, $bVisible = true)
{
$sModuleDir = $sDirectory.'/'.$sModuleName;
SetupUtils::builddir($sModuleDir);
$this->aFileToClean[] = $sDirectory;
$sModuleFileName = $sModuleDir.'/module.'.$sModuleName.'.php';
$sMandatory = var_export($bMandatory, true);
$sVisible = var_export($bVisible, true);
file_put_contents(
$sModuleFileName,
<<<PHP
<?php
SetupWebPage::AddModule(
__FILE__,
"$sModuleName/$sModuleVersion",
[
'label' => "$sModuleName",
'dependencies' => [],
'mandatory' => $sMandatory,
'visible' => $sVisible,
'datamodel' => [],
'data.struct' => [],
'data.sample' => [],
'doc.manual_setup' => '',
'doc.more_information' => '',
]
);
PHP
);
}
}

View File

@@ -55,6 +55,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node1 - A missing extension should always be disabled and unchecked, even a mandatory extension included in package' => [
@@ -81,6 +82,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => true,
'remote' => false,
'already_included' => false,
],
],
'#node2 - A mandatory extension included in package should be checked and disabled even if the "disable uninstallation check" flag is set' => [
@@ -106,6 +108,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => true,
'remote' => false,
'already_included' => false,
],
],
'#node3 - An installed non uninstallable sub extension should force its parent to be checked and disabled' => [
@@ -140,6 +143,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node3 - An installed remote sub extension should force its parent to be checked and disabled' => [
@@ -175,6 +179,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node4 - An installed uninstallable sub extension should force its parent to be checked but not disabled' => [
@@ -209,6 +214,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node4 - An installed non uninstallable sub extension should force its parent to be checked but not disabled if the "disable uninstallation check" flag is set' => [
@@ -243,6 +249,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node4 - An installed remote sub extension should force its parent to be checked but not disabled if the "disable uninstallation check" flag is set' => [
@@ -278,6 +285,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node5 - A non installed extension with missing dependencies should be not checked and disabled' => [
@@ -305,6 +313,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => true,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node5 - A non installed extension with missing dependencies should be not checked and disabled even with force uninstall' => [
@@ -332,6 +341,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => true,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node6 - An installed extension with missing dependencies and without force uninstall should be checked and disabled' => [
@@ -359,6 +369,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => true,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node7 - An installed extension with missing dependencies and with force uninstall should be checked and enabled' => [
@@ -386,6 +397,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => true,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node8 - An installed but not selected extension with missing dependencies and with force uninstall should be unchecked and enabled' => [
@@ -413,6 +425,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => true,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node9 - A not selected, not installed extension should not be checked and be enabled' => [
@@ -436,6 +449,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node10 - A selected but not installed extension should be checked and enabled' => [
@@ -459,6 +473,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node11 - An installed but not selected extension should not be checked and be enabled' => [
@@ -482,6 +497,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node12 - An installed and selected extension should be checked and enabled' => [
@@ -506,6 +522,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node13 - An installed non uninstallable extension should be checked and disabled' => [
@@ -529,6 +546,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node13 - An installed remote extension should be checked and disabled' => [
@@ -553,6 +571,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => true,
'already_included' => false,
],
],
'#node14 - An installed but not selected non uninstallable extension should be checked and enabled if the "disable uninstallation check" flag is set' => [
@@ -576,6 +595,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node14 - An installed but not selected remote extension should be checked and enabled if the "disable uninstallation check" flag is set' => [
@@ -600,6 +620,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => true,
'already_included' => false,
],
],
'#node15 - An installed non uninstallable extension should be checked and enabled if the "disable uninstallation check" flag is set' => [
@@ -623,6 +644,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node15 - An installed remote extension should be checked and enabled if the "disable uninstallation check" flag is set' => [
@@ -647,9 +669,10 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => true,
'already_included' => false,
],
],
'#node16 - A non installed non uninstallable sub extension should not force its parent flags' => [
'#node16 - A non installed sub extension should not force its parent flags' => [
'aExtensionsOnDiskOrDb' => [
'itop-ext1' => [
'installed' => true,
@@ -681,6 +704,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node16 - A non installed remote sub extension should not force its parent flags' => [
@@ -716,6 +740,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node13 - An installed non uninstallable extension should be checked and disabled when the "disable uninstallation check" flag is not set, even if it is not selected' => [
@@ -739,6 +764,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
'#node6 - An installed extension with missing dependencies without force uninstall should be checked and disabled, even if it is not selected' => [
@@ -766,6 +792,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
'dependency_issue' => true,
'mandatory' => false,
'remote' => false,
'already_included' => false,
],
],
];
@@ -781,6 +808,52 @@ class WizStepModulesChoiceTest extends ItopTestCase
$this->assertEquals($aExpectedFlags, $aFlags);
}
/**
* #node17 an extension with all of its modules already selected in previous steps should be checked and disabled, with the "already_included" flag set to true
*/
public function testComputeChoiceFlagsShouldReturnAlreadyIncludedWhenAllModulesAreInPreviousSteps(): void
{
$this->oWizard->SetParameter('source_dir', __DIR__.'/ressources');
$this->oWizard->SetParameter('selected_components', json_encode([
['_0' => '_0'],//Selecting itop-config-mgmt-core
]));
$oWizStepModulesChoice = new WizStepModulesChoiceFake($this->oWizard, '1');
$oWizStepModulesChoice->setExtensionMap(iTopExtensionsMapFake::createFromArray([
'itop-ext1' => [
'installed' => false,
'modules' => [
'itop-config-mgmt',
'itop-attachments',
],
],
]));
$aFlags = $oWizStepModulesChoice->ComputeChoiceFlags(
[
'extension_code' => 'itop-ext1',
'uninstallable' => true,
],
'_0',
[],
false,
false
);
$aExpectedFlags = [
'uninstallable' => true,
'missing' => false,
'installed' => false,
'disabled' => true,
'checked' => true,
'dependency_issue' => false,
'mandatory' => false,
'remote' => false,
'already_included' => true,
];
$this->assertEquals($aExpectedFlags, $aFlags);
}
public function ProviderCanMoveForwardFromChoiceFlags(): array
{
return [

View File

@@ -4,30 +4,30 @@ Removed from disk
Yes : Forced uninstallation, unchecked & disabled .color_red #node1
No : Mandatory & part of package
Yes : Forced installation, checked & disabled .color_orange #node2
No : Has sub options
Yes : Sub option checked
No : Modules included in package
Yes : Forced installation, checked & disabled .color_orange #node17
No : Has sub options & Sub option checked
Yes : Sub option disabled
Yes : checked & disabled .color_orange #node3
No : force checked .color_yellow #node4
#node16 No : (Dependency issue)
No : Dependency issue
Yes : Installed
No : unchecked & disabled .color_orange #node5
Yes : Force uninstall
No : checked & disabled .color_orange #node6
Yes : Selected
Yes : checked & enabled .color_green #node7
No : unchecked & enabled .color_green #node8
No : Installed
No : Selected
No : unchecked & enabled .color_green #node9
Yes : checked & enabled .color_green #node10
Yes : Remote | cannot_be_uninstalled
No : Selected
No : unchecked & enabled .color_green #node11
Yes : checked & enabled .color_green #node12
No : checked & enabled .color_green #node4
No : Dependency issue #node16
Yes : Installed
No : unchecked & disabled .color_orange #node5
Yes : Force uninstall
No : checked & disabled .color_orange #node13
No : checked & disabled .color_orange #node6
Yes : Selected
No : unchecked & enabled .color_green #node14
Yes : checked & enabled .color_green #node15
Yes : checked & enabled .color_green #node7
No : unchecked & enabled .color_green #node8
No : Installed
No : Selected
No : unchecked & enabled .color_green #node9
Yes : checked & enabled .color_green #node10
Yes : Remote | cannot_be_uninstalled
No : Selected
No : unchecked & enabled .color_green #node11
Yes : checked & enabled .color_green #node12
Yes : Force uninstall
No : checked & disabled .color_orange #node13
Yes : Selected
No : unchecked & enabled .color_green #node14
Yes : checked & enabled .color_green #node15