@@ -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 }
0 commit comments