Skip to content

Commit d4183ac

Browse files
authored
N°8762 - Allow extensions uninstallation at setup
1 parent c5fb116 commit d4183ac

File tree

6 files changed

+112
-68
lines changed

6 files changed

+112
-68
lines changed

setup/extensionsmap.class.inc.php

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ public function __construct()
110110
$this->bVisible = true;
111111
$this->aMissingDependencies = array();
112112
}
113+
114+
/**
115+
* @since 3.3.0
116+
* @return bool
117+
*/
118+
public function CanBeUninstalled()
119+
{
120+
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
121+
$bUninstallable = $aModuleInfo['uninstallable'] === 'yes';
122+
if (!$bUninstallable) {
123+
return false;
124+
}
125+
}
126+
return true;
127+
}
113128
}
114129

115130
/**
@@ -253,6 +268,22 @@ protected function AddExtension(iTopExtension $oNewExtension)
253268
$this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
254269
}
255270

271+
/**
272+
* @since 3.3.0
273+
* @param string $sExtensionCode
274+
*
275+
* @return \iTopExtension|null
276+
*/
277+
public function Get(string $sExtensionCode):?iTopExtension
278+
{
279+
foreach($this->aExtensions as $oExtension) {
280+
if ($oExtension->sCode === $sExtensionCode) {
281+
return $oExtension;
282+
}
283+
}
284+
return null;
285+
}
286+
256287
/**
257288
* Read (recursively) a directory to find if it contains extensions (or modules)
258289
*
@@ -277,8 +308,7 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null)
277308
$aSubDirectories = array();
278309

279310
// First check if there is an extension.xml file in this directory
280-
if (is_readable($sSearchDir.'/extension.xml'))
281-
{
311+
if (is_readable($sSearchDir.'/extension.xml')) {
282312
$oXml = new XMLParameters($sSearchDir.'/extension.xml');
283313
$oExtension = new iTopExtension();
284314
$oExtension->sCode = $oXml->Get('extension_code');
@@ -315,44 +345,43 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null)
315345
// If we are not already inside a formal extension, then the module itself is considered
316346
// as an extension, otherwise, the module is just added to the list of modules belonging
317347
// to this extension
318-
$sModuleId = $aModuleInfo[1];
348+
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
319349
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
320-
if ($sModuleVersion == '')
321-
{
350+
if ($sModuleVersion == '') {
322351
// Provide a default module version since version is mandatory when recording ExtensionInstallation
323352
$sModuleVersion = '0.0.1';
324353
}
354+
$aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['uninstallable'] ??= 'yes';
325355

326356
if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension)) {
327357
// Already inside an extension, let's add this module the list of modules belonging to this extension
328358
$this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName;
329359
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
330-
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[2];
360+
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
331361
}
332-
else
333-
{
362+
else {
334363
// Not already inside an folder containing an 'extension.xml' file
335364

336365
// Ignore non-visible modules and auto-select ones, since these are never prompted
337366
// as a choice to the end-user
338367
$bVisible = true;
339-
if (!$aModuleInfo[2]['visible'] || isset($aModuleInfo[2]['auto_select']))
368+
if (!$aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['visible'] || isset($aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['auto_select']))
340369
{
341370
$bVisible = false;
342371
}
343372

344373
// Let's create a "fake" extension from this module (containing just this module) for backwards compatibility
345374
$oExtension = new iTopExtension();
346375
$oExtension->sCode = $sModuleName;
347-
$oExtension->sLabel = $aModuleInfo[2]['label'];
376+
$oExtension->sLabel = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['label'];
348377
$oExtension->sDescription = '';
349378
$oExtension->sVersion = $sModuleVersion;
350379
$oExtension->sSource = $sSource;
351-
$oExtension->bMandatory = $aModuleInfo[2]['mandatory'];
352-
$oExtension->sMoreInfoUrl = $aModuleInfo[2]['doc.more_information'];
380+
$oExtension->bMandatory = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['mandatory'];
381+
$oExtension->sMoreInfoUrl = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['doc.more_information'];
353382
$oExtension->aModules = array($sModuleName);
354383
$oExtension->aModuleVersion[$sModuleName] = $sModuleVersion;
355-
$oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[2];
384+
$oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
356385
$oExtension->sSourceDir = $sSearchDir;
357386
$oExtension->bVisible = $bVisible;
358387
$this->AddExtension($oExtension);
@@ -452,6 +481,7 @@ public function MarkAsChosen($sExtensionCode, $bMark = true)
452481
}
453482
}
454483

484+
455485
/**
456486
* Tells if a given extension(code) is marked as chosen
457487
* @param string $sExtensionCode
@@ -572,46 +602,39 @@ public function IsExtensionObsoletedByAnother(iTopExtension $oExtension)
572602
public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MANUAL)
573603
{
574604
$aSignatures = $this->GetOldExtensionsSignatures();
575-
foreach($aSignatures as $sExtensionCode => $aExtensionSignatures)
576-
{
605+
foreach($aSignatures as $sExtensionCode => $aExtensionSignatures) {
577606
$bFound = false;
578-
foreach($aExtensionSignatures['versions'] as $sVersion => $aModules)
579-
{
607+
foreach($aExtensionSignatures['versions'] as $sVersion => $aModules) {
580608
$bInstalled = true;
581-
foreach($aModules as $sModuleId)
582-
{
583-
if(!$this->ModuleIsPresent($sModuleId, $sInSourceOnly))
584-
{
609+
foreach($aModules as $sModuleId) {
610+
if(!$this->ModuleIsPresent($sModuleId, $sInSourceOnly)) {
585611
$bFound = false;
586612
break; // One missing module is enough to determine that the extension/version is not present
587613
}
588-
else
589-
{
590-
$bInstalled = $bInstalled && (!$this->ModuleIsInstalled($sModuleId, $sInSourceOnly));
614+
else {
615+
$bInstalled = $bInstalled && $this->ModuleIsInstalled($sModuleId, $sInSourceOnly);
591616
$bFound = true;
592617
}
593618
}
594619
if ($bFound) break; // The current version matches the signature
595620
}
596621

597-
if ($bFound)
598-
{
622+
if ($bFound) {
599623
$oExtension = new iTopExtension();
600624
$oExtension->sCode = $sExtensionCode;
601625
$oExtension->sLabel = $aExtensionSignatures['label'];
602626
$oExtension->sSource = $sInSourceOnly;
603627
$oExtension->sDescription = $aExtensionSignatures['description'];
604628
$oExtension->sVersion = $sVersion;
605629
$oExtension->aModules = array();
606-
if ($bInstalled)
607-
{
630+
if ($bInstalled) {
608631
$oExtension->sInstalledVersion = $sVersion;
609632
$oExtension->bMarkedAsChosen = true;
610633
}
611-
foreach($aModules as $sModuleId)
612-
{
634+
foreach($aModules as $sModuleId) {
613635
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
614636
$oExtension->aModules[] = $sModuleName;
637+
$oExtension->aModuleInfo[$sModuleName] = $this->aExtensions[$sModuleId]->aModuleInfo[$sModuleName];
615638
}
616639
$this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension);
617640
}

setup/modulediscovery.class.inc.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ protected static function ListModuleFiles($sRelDir, $sRootDir)
520520
$sModuleFilePath = $sDirectory.'/'.$sFile;
521521
try {
522522
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sDirectory.'/'.$sFile);
523-
SetupWebPage::AddModule($sModuleFilePath, $aModuleInfo[1], $aModuleInfo[2]);
523+
SetupWebPage::AddModule($sModuleFilePath, $aModuleInfo[ModuleFileReader::MODULE_INFO_ID], $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]);
524524
} catch(ModuleFileReaderException $e){
525525
continue;
526526
}

setup/modulediscovery/ModuleFileReader.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class ModuleFileReader {
3232
"method_exists"
3333
];
3434

35+
const MODULE_INFO_PATH = 0;
36+
const MODULE_INFO_ID = 1;
37+
const MODULE_INFO_CONFIG = 2;
38+
3539
const STATIC_CALLWHITELIST=[
3640
"utils::GetItopVersionWikiSyntax"
3741
];
@@ -168,7 +172,7 @@ public function ReadModuleFileInformationUnsafe(string $sModuleFilePath) : array
168172
private function CompleteModuleInfoWithFilePath(array &$aModuleInfo)
169173
{
170174
if (count($aModuleInfo)==3) {
171-
$aModuleInfo[2]['module_file_path'] = $aModuleInfo[0];
175+
$aModuleInfo[static::MODULE_INFO_CONFIG]['module_file_path'] = $aModuleInfo[static::MODULE_INFO_PATH];
172176
}
173177
}
174178

@@ -255,9 +259,9 @@ private function GetModuleInformationFromAddModuleCall(string $sModuleFilePath,
255259
}
256260

257261
return [
258-
$sModuleFilePath,
259-
$sModuleId,
260-
$aModuleConfig,
262+
static::MODULE_INFO_PATH => $sModuleFilePath,
263+
static::MODULE_INFO_ID => $sModuleId,
264+
static::MODULE_INFO_CONFIG => $aModuleConfig,
261265
];
262266
}
263267

setup/moduleinstallation.class.inc.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static function Init()
4848
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values" => null, "sql" => "installed", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
4949
MetaModel::Init_AddAttribute(new AttributeText("comment", array("allowed_values" => null, "sql" => "comment", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
5050
MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass" => "ModuleInstallation", "jointype" => "", "allowed_values" => null, "sql" => "parent_id", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => array())));
51-
51+
MetaModel::Init_AddAttribute(new AttributeEnum("uninstallable", array("allowed_values"=>new ValueSetEnum('yes,no,maybe'), "sql"=>"uninstallable", "default_value"=>'yes', "is_null_allowed"=>false, "depends_on"=>array())));
5252

5353
// Display lists
5454
MetaModel::Init_SetZListItems('details', array('name', 'version', 'installed', 'comment', 'parent_id')); // Attributes to be displayed for the complete details
@@ -87,6 +87,7 @@ public static function Init()
8787
MetaModel::Init_AddAttribute(new AttributeString("label", array("allowed_values"=>null, "sql"=>"label", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
8888
MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
8989
MetaModel::Init_AddAttribute(new AttributeString("source", array("allowed_values"=>null, "sql"=>"source", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
90+
MetaModel::Init_AddAttribute(new AttributeEnum("uninstallable", array("allowed_values"=>new ValueSetEnum('yes,no,maybe'), "sql"=>"uninstallable", "default_value"=>'yes', "is_null_allowed"=>false, "depends_on"=>array())));
9091
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values"=>null, "sql"=>"installed", "default_value"=>'NOW()', "is_null_allowed"=>false, "depends_on"=>array())));
9192

9293

setup/runtimeenv.class.inc.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,7 @@ public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelect
756756
$aModuleData = $aAvailableModules[$sModuleId];
757757
$sName = $sModuleId;
758758
$sVersion = $aModuleData['version_code'];
759+
$sUninstallable = $aModuleData['uninstallable'] ?? 'yes';
759760
$aComments = array();
760761
$aComments[] = $sShortComment;
761762
if ($aModuleData['mandatory']) {
@@ -783,6 +784,7 @@ public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelect
783784
$oInstallRec->Set('comment', $sComment);
784785
$oInstallRec->Set('parent_id', $iMainItopRecord);
785786
$oInstallRec->Set('installed', $iInstallationTime);
787+
$oInstallRec->Set('uninstallable', $sUninstallable);
786788
$oInstallRec->DBInsertNoReload();
787789
}
788790

@@ -805,6 +807,7 @@ public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelect
805807
$oInstallRec->Set('label', $oExtension->sLabel);
806808
$oInstallRec->Set('version', $oExtension->sVersion);
807809
$oInstallRec->Set('source', $oExtension->sSource);
810+
$oInstallRec->Set('uninstallable', $oExtension->CanBeUninstalled() ? 'yes' : 'no');
808811
$oInstallRec->Set('installed', $iInstallationTime);
809812
$oInstallRec->DBInsertNoReload();
810813
}

0 commit comments

Comments
 (0)