Compare commits

..

8 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
30 changed files with 345 additions and 186 deletions

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

@@ -159,7 +159,7 @@ class EventListener implements iEventServiceSetup
while ($oTrigger = $oTriggerSet->Fetch()) {
try {
$aContextArgs = $aTriggerContextArgs;
if (MetaModel::IsValidAttCode(get_class($oTrigger), 'file_in_email') && $oTrigger->Get('file_in_email')) {
if ($oTrigger->Get('file_in_email')) {
$aContextArgs['attachments'] = [$oDocument];
}
$oTrigger->DoActivate($aContextArgs);

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

@@ -62,22 +62,20 @@ class iTopExtensionsMap
*
* @throws \Exception
*/
private function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [], ?string $sAppRootForTests = null, bool $bNoScan = false)
private function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [], ?string $sAppRootForTests = null)
{
$this->aExtensions = [];
$this->aExtensionsByCode = [];
$this->aScannedDirs = [];
if (! $bNoScan) {
$sAppRoot = $sAppRootForTests ?? APPROOT;
$this->ScanDisk($sFromEnvironment, $sAppRoot);
$sAppRoot = $sAppRootForTests ?? APPROOT;
$this->ScanDisk($sFromEnvironment, $sAppRoot);
foreach ($aExtraDirs as $sDir) {
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE, bIsRootDir: true);
}
$this->CheckDependencies($sAppRoot);
foreach ($aExtraDirs as $sDir) {
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE, bIsRootDir: true);
}
$this->CheckDependencies($sAppRoot);
}
/**
@@ -263,7 +261,7 @@ class iTopExtensionsMap
foreach ($aExtensionCodes as $sCode => $sLabel) {
$sRealCode = is_int($sCode) ? $sLabel : $sCode;
/** @var ?\iTopExtension $oExtension */
/** @var \iTopExtension $oExtension */
$oExtension = $this->GetFromExtensionCode($sRealCode);
if (!is_null($oExtension)) {
$aRemovedExtension [] = $oExtension;
@@ -300,7 +298,7 @@ class iTopExtensionsMap
$aSubDirectories = [];
// First check if there is an extension.xml file in this directory
if ($sParentExtensionId === null && is_readable($sSearchDir.'/extension.xml')) {
if (is_readable($sSearchDir.'/extension.xml')) {
$oXml = new XMLParameters($sSearchDir.'/extension.xml');
$oExtension = new iTopExtension();
$oExtension->sCode = $oXml->Get('extension_code');
@@ -359,7 +357,7 @@ class iTopExtensionsMap
$oExtension->sVersion = $sModuleVersion;
$oExtension->sSource = $sSource;
$oExtension->bMandatory = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['mandatory'];
$oExtension->sMoreInfoUrl = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['doc.more_information'] ?? '';
$oExtension->sMoreInfoUrl = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['doc.more_information'];
$oExtension->aModules = [$sModuleName];
$oExtension->aModuleVersion[$sModuleName] = $sModuleVersion;
$oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
@@ -405,15 +403,27 @@ class iTopExtensionsMap
return [];
}
$oiTopExtensionsMap = new iTopExtensionsMap(bNoScan: true);
$oiTopExtensionsMap->ReadDir($sSearchDir, iTopExtension::SOURCE_REMOTE, bIsRootDir: true);
$aExtensions = [];
$hDir = opendir($sSearchDir);
if ($hDir !== false) {
$aRes = [];
foreach ($oiTopExtensionsMap->aExtensionsByCode as $sCode => $oExtension) {
/** @var iTopExtension $oExtension */
$aRes[$sCode] = $oExtension->sLabel;
// Then scan the other files and subdirectories
while (($sDir = readdir($hDir)) !== false) {
if (($sDir === '.') || ($sDir === '..') || !is_dir($sSearchDir.$sDir)) {
continue;
}
// First check if there is an extension.xml file in this directory
if (is_readable($sSearchDir.$sDir.'/extension.xml')) {
$oXml = new XMLParameters($sSearchDir.$sDir.'/extension.xml');
$aExtensions[$oXml->Get('extension_code')] = $oXml->Get('label');
}
}
closedir($hDir);
}
return $aRes;
return $aExtensions;
}
/**
@@ -743,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

@@ -11,8 +11,8 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
public function __construct(WizardController $oWizard, $sCurrentState)
{
$this->oRuntimeEnv = new RunTimeEnvironment($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV), false);
$sBuildConfigFile = APPCONF.$this->oRuntimeEnv->GetFinalEnv().'/'.ITOP_CONFIG_FILE;
$this->oRuntimeEnv = new RunTimeEnvironment($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV));
$sBuildConfigFile = APPCONF.$this->oRuntimeEnv->GetBuildEnv().'/'.ITOP_CONFIG_FILE;
$this->oConfig = new Config($sBuildConfigFile);
$oWizard->SetParameter('previous_version_dir', APPROOT);
@@ -36,8 +36,6 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
// should be done at the end
parent::__construct($oWizard, $sCurrentState, false);
$this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->oRuntimeEnv->GetBuildEnv());
}
/**

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

@@ -10,7 +10,6 @@ parameters:
bootstrapFiles:
- ../../../approot.inc.php
- ../../../bootstrap.inc.php
- ../../../tests/php-unit-tests/vendor/autoload.php
scanFiles:
# Files necessary as they contain some declarations (constants, classes, functions, ...)
@@ -37,5 +36,6 @@ parameters:
- ../../../env-php-unit-tests (?) # Irrelevant as it will either already be in `env-production` or might be desynchronized from `env-production`
- ../../../env-toolkit (?) # Irrelevent as it will either already be in `env-production` or might be desynchronized from `env-production` (for local run only, not useful in the CI)
- ../../../tests (?) # Exclude tests for now
- ../../../toolkit (?) # Exlclude toolkit for now
#- ../../../tests (?) # Exclude tests for now
- ../../../toolkit (?) # Exclude toolkit for now
- ../../../setup/compat # Exclude fake DOMDocument & DOMElement declarations

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

@@ -420,130 +420,4 @@ XML;
return $aRes;
}
public function testGetExtensionsFromDir_MonoModuleExtensionWithXmlDefinition()
{
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$sTestDir = $this->PrepareTestDir();
$this->CreateExtensionFolder($sTestDir, "ext1", "ext_code1", "1.5.9");
$this->CreateModule($sTestDir, "module1", "module_code1", "9.5.1");
self::assertEquals(['ext_code1' => 'ext1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir));
}
public function testGetExtensionsFromDir_MultiModuleExtension()
{
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$sTestDir = $this->PrepareTestDir();
$this->CreateExtensionFolder($sTestDir, "ext1", "ext_code1", "1.5.9");
foreach ([1, 2, 3] as $i) {
$this->CreateModule($sTestDir, "module{$i}", "module_code{$i}", "9.5.{$i}");
}
self::assertEquals(['ext_code1' => 'ext1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir));
}
public function testGetExtensionsFromDir_MultiModuleExtensionWithOneXMLDefPerModule()
{
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$sTestDir = $this->PrepareTestDir();
$this->CreateExtensionFolder($sTestDir, "ext1", "ext_code1", "1.5.9");
foreach ([1, 2, 3] as $i) {
if (! is_dir("$sTestDir/module_code{$i}")) {
mkdir("$sTestDir/module_code{$i}");
}
$this->CreateExtensionFolder("$sTestDir/module_code{$i}", "ext{$i}", "ext_code{$i}", "9.5.{$i}");
$this->CreateModule($sTestDir, "module{$i}", "module_code{$i}", "9.5.{$i}");
}
self::assertEquals(['ext_code1' => 'ext1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir));
}
public function testGetExtensionsFromDir_FurtherMonoModuleExtensionsWithoutXmlDefinition()
{
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$sTestDir = $this->PrepareTestDir();
foreach ([1, 2, 3] as $i) {
$this->CreateModule($sTestDir, "module{$i}", "module_code{$i}", "9.5.{$i}");
}
$expected = [
'module_code1' => 'module1',
'module_code2' => 'module2',
'module_code3' => 'module3',
];
self::assertEquals($expected, $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir));
}
public function testGetExtensionsFromDir_MonoModuleExtensionWithoutXmlDefinition()
{
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$sTestDir = $this->PrepareTestDir();
$this->CreateModule($sTestDir, "module1", "module_code1", "9.5.1");
self::assertEquals(['module_code1' => 'module1'], $oiTopExtensionsMap->GetExtensionsFromDir($sTestDir));
}
private function PrepareTestDir(): string
{
$sTestDir = tempnam(sys_get_temp_dir(), "ext_map");
unlink($sTestDir);
mkdir($sTestDir, 0777, true);
$this->aFileToClean[] = $sTestDir;
return $sTestDir;
}
private function CreateExtensionFolder(string $sDir, string $sExtensionLabel, string $sExtensionCode, string $sExtensionVersion)
{
$sXmlDef = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<extension format="1.0">
<extension_code>$sExtensionCode</extension_code>
<label><![CDATA[$sExtensionLabel]]></label>
<description><![CDATA[BLABLA]]></description>
<version>$sExtensionVersion</version>
<modules type="array">
<module>
<id>aaa</id>
<version>tags/6.6.6</version>
</module>
</modules>
<release_date>2023-07-19</release_date>
<itop_version_min>3.2.0</itop_version_min>
<status></status>
<mandatory>false</mandatory>
<more_info_url></more_info_url>
</extension>
XML;
file_put_contents($sDir.'/extension.xml', $sXmlDef);
}
private function CreateModule(string $sDir, string $sModuleLabel, string $sModuleCode, string $sVersion)
{
$sModuleDir = $sDir."/$sModuleCode";
if (! is_dir($sModuleDir)) {
mkdir($sModuleDir, 0777, true);
}
$sModuleContent = <<<PHP
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'$sModuleCode/$sVersion',
[
'label' => '$sModuleLabel',
'category' => 'authentication',
'dependencies' => [],
'mandatory' => true,
'visible' => true,
'datamodel' => [],
]
);
PHP;
file_put_contents($sModuleDir."/module.{$sModuleCode}.php", $sModuleContent);
}
}

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