Compare commits

...

27 Commits

Author SHA1 Message Date
jf-cbd
1e7238a791 Removing user counting from iTop (to put it in itop-system-information) 2026-06-17 17:04:07 +02:00
jf-cbd
2803c296be Removing user counting from iTop (to put it in itop-system-information) 2026-06-17 17:04:06 +02:00
jf-cbd
17767b621f Refactor 2026-06-17 17:04:06 +02:00
jf-cbd
c57cd8db9f Align dict key with the others 2026-06-17 17:04:06 +02:00
jf-cbd
d29f6bb9c1 Remove assertion in test 2026-06-17 17:04:06 +02:00
jf-cbd
0500aec7c7 Check if class UserToken exists 2026-06-17 17:04:05 +02:00
jf-cbd
3172122a9a Check if class UserToken exists 2026-06-17 17:04:05 +02:00
jf-cbd
ba54cae217 Check if class UserToken exists 2026-06-17 17:04:05 +02:00
jf-cbd
697e3db497 Revert format 2026-06-17 17:04:02 +02:00
jf-cbd
99350c4c8b CS fixer 2026-06-17 17:03:56 +02:00
jf-cbd
55660bf461 Adapt test after test method signature changed (on rebase). 2026-06-17 17:03:56 +02:00
jf-cbd
28c96f0536 Improve test readability 2026-06-17 17:03:56 +02:00
jf-cbd
37de3527ec Add more unit tests 2026-06-17 17:03:55 +02:00
jf-cbd
f6dd338254 With business portal users 2026-06-17 17:03:55 +02:00
jf-cbd
64c9cb5898 WIP 2026-06-17 17:03:52 +02:00
jf-cbd
71d5d87fe5 Improve test method to be able to get login OR id of the created user 2026-06-17 17:03:42 +02:00
jf-cbd
b2329939b6 WIP 2026-06-17 17:03:42 +02:00
jf-cbd
90df74f397 WIP improving quota computing 2026-06-17 17:03:41 +02:00
jf-cbd
26a8205198 Rollback 2026-06-17 17:03:41 +02:00
jf-cbd
d60ed6ebbe WIP 2026-06-17 17:03:41 +02:00
jf-cbd
3dfd8d3aa7 Update profiles definition 2026-06-17 17:03:41 +02:00
jf-cbd
f13b0acefc WIP on new user read-only profiles 2026-06-17 17:03:40 +02:00
odain
6a8dc159c1 revert 75433d3390: Enhance unattended CLI to log fatal error 2026-06-17 15:38:22 +02:00
odain
2930a122c6 N°9144 - fix commit operation rmdir conf/production-build failed as not always empty 2026-06-17 15:21:30 +02:00
odain
75433d3390 Enhance unattended CLI to log fatal error just like setup wizardinstead of silent failure 2026-06-17 15:12:46 +02:00
odain
d4d6fa149d N°9412 - revert fresh-install.xml changes 2026-06-17 14:31:50 +02:00
odain-cbd
207ac373d0 N°9678 - Datamodel not added after extension management setup (#936)
* N°9678 - Datamodel not added after extension management setup

* N°9678 - fix selecting extensions from extensions folder in extension mgt view

* cleanup: remove cached stuff in runtimeenv

* N°9678 - Refactor iTopExtensionsMap instantiation to use GetExtensionsMap method

* N°9678 - Refactor data model compilation and improve directory scanning logic

* N°9678 - Handle MySQLException in ModuleInstallationRepository and remove unnecessary error logging in WizStepModulesChoice

---------

Co-authored-by: Eric Espie <eric.espie@combodo.com>
2026-06-17 10:22:05 +02:00
35 changed files with 903 additions and 253 deletions

View File

@@ -145,7 +145,7 @@ class DataFeatureRemovalController extends Controller
$bForceCompilation = Session::Get('bForceCompilation', false);
try {
$this->Compile($aRemoveExtensionCodes, $bForceCompilation);
$this->Compile($aAddedExtensions, $aRemoveExtensionCodes, $bForceCompilation);
} catch (CoreException $e) {
$aParams['DataFeatureRemovalErrorMessage'] = $e->getHtmlDesc();
$this->DisplayPage($aParams, 'AnalysisResult');
@@ -162,7 +162,7 @@ class DataFeatureRemovalController extends Controller
$aSelectedExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions));
$aHiddenInputs['selected_extensions'] = $this->ConvertIntoSetupFormat($aSelectedExtensions);
$oRunTimeEnvironment = $this->GetRuntimeEnvironment($aRemovedExtensions);
$oRunTimeEnvironment = $this->GetRuntimeEnvironment($aAddedExtensions, $aRemovedExtensions);
$aSearchDirs = [$oRunTimeEnvironment->GetBuildDir()];
$aSelectedModules = $oRunTimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions, $aSearchDirs);
$aHiddenInputs['selected_modules'] = $this->ConvertIntoSetupFormat($aSelectedModules);
@@ -213,13 +213,14 @@ class DataFeatureRemovalController extends Controller
}
/**
* @param array $aAddedExtensions
* @param array $aRemovedExtensions
* @param bool $bForceCompilation
* @return void
* @throws \ConfigException
* @throws \CoreException
*/
private function Compile(array $aRemovedExtensions, bool $bForceCompilation = true): void
private function Compile(array $aAddedExtensions, array $aRemovedExtensions, bool $bForceCompilation = true): void
{
$sSourceEnv = MetaModel::GetEnvironment();
$sBuildDir = APPROOT."/env-$sSourceEnv-build";
@@ -234,15 +235,15 @@ class DataFeatureRemovalController extends Controller
null,
['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")]
);
$this->GetRuntimeEnvironment($aRemovedExtensions)->CompileFrom($sSourceEnv);
$this->GetRuntimeEnvironment($aAddedExtensions, $aRemovedExtensions)->CompileFrom($sSourceEnv);
}
}
private function GetRuntimeEnvironment(array $aRemovedExtensions): RunTimeEnvironment
private function GetRuntimeEnvironment(array $aAddedExtensions, array $aRemovedExtensions): RunTimeEnvironment
{
if (is_null($this->oRuntimeEnvironment)) {
$sSourceEnv = MetaModel::GetEnvironment();
$this->oRuntimeEnvironment = new DryRemovalRuntimeEnvironment($sSourceEnv, $aRemovedExtensions);
$this->oRuntimeEnvironment = new DryRemovalRuntimeEnvironment($sSourceEnv, $aAddedExtensions, $aRemovedExtensions);
}
return $this->oRuntimeEnvironment;

View File

@@ -66,7 +66,7 @@ class DataFeatureRemoverExtensionService
public function GetExtensionMap(): iTopExtensionsMap
{
if (is_null($this->oMap)) {
$this->oMap = new iTopExtensionsMap();
$this->oMap = iTopExtensionsMap::GetExtensionsMap();
$this->oMap->LoadInstalledExtensionsFromDatabase(MetaModel::GetConfig());
}
return $this->oMap;

View File

@@ -163,7 +163,7 @@ final class CoreUpdater
$oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/);
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad');
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
$oExtensionsMap = new iTopExtensionsMap();
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
// Default choices = as before
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {

View File

@@ -20,7 +20,7 @@ function DisplayStatus(WebPage $oPage)
if (is_dir($sPath)) {
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
}
$oExtensionsMap = new iTopExtensionsMap(ITOP_DEFAULT_ENV, $aExtraDirs);
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap(ITOP_DEFAULT_ENV);
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
@@ -154,7 +154,7 @@ function DoInstall(WebPage $oPage)
if (is_dir($sPath)) {
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
}
$oExtensionsMap = new iTopExtensionsMap(ITOP_DEFAULT_ENV, $aExtraDirs);
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap(ITOP_DEFAULT_ENV);
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {

View File

@@ -193,7 +193,7 @@ function collect_configuration()
}
// iTop Installation Options, i.e. "Extensions"
$oExtensionMap = new iTopExtensionsMap();
$oExtensionMap = iTopExtensionsMap::GetExtensionsMap();
$oExtensionMap->LoadChoicesFromDatabase($oConfig);
$aConfiguration['itop_extensions'] = [];
foreach ($oExtensionMap->GetChoices() as $oExtension) {

View File

@@ -41,7 +41,7 @@ function GetExtensionInfoComponent(iTopExtension $oExtension): UIBlock
}
try {
$oExtensionsMap = new iTopExtensionsMap();
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage(Dict::S('iTopHub:InstalledExtensions')));

View File

@@ -208,7 +208,7 @@ class HubController
// Record the installation so that the "about box" knows about the installed modules
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
$oExtensionsMap = new iTopExtensionsMap();
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
// Default choices = as before
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);

View File

@@ -85,6 +85,13 @@
<class id="Attachment"/>
</classes>
</group>
<group id="Ticket" _delta="define">
<classes>
<class id="Ticket"/>
<class id="WorkOrder"/>
<class id="Attachment"/>
</classes>
</group>
<group id="Portal" _delta="define">
<classes>
<class id="lnkFunctionalCIToTicket"/>
@@ -205,6 +212,60 @@
</group>
</groups>
<profiles>
<profile id="5500" _delta="define">
<name>Configuration ReadOnly</name>
<description>This read-only profile allows to see CIs objects.</description>
<groups>
<group id="Configuration">
<actions>
<action id="action:read">allow</action>
<action id="action:bulk read">allow</action>
</actions>
</group>
<group id="General">
<actions>
<action id="action:read">allow</action>
<action id="action:bulk read">allow</action>
</actions>
</group>
</groups>
</profile>
<profile id="5501" _delta="define">
<name>Ticket ReadOnly</name>
<description>This read-only profile allows to see Ticket objects.</description>
<groups>
<group id="Ticket">
<actions>
<action id="action:read">allow</action>
<action id="action:bulk read">allow</action>
</actions>
</group>
<group id="General">
<actions>
<action id="action:read">allow</action>
<action id="action:bulk read">allow</action>
</actions>
</group>
</groups>
</profile>
<profile id="5502" _delta="define">
<name>Service Catalog ReadOnly</name>
<description>This read-only profile allows to see Service Catalog objects.</description>
<groups>
<group id="Service">
<actions>
<action id="action:read">allow</action>
<action id="action:bulk read">allow</action>
</actions>
</group>
<group id="General">
<actions>
<action id="action:read">allow</action>
<action id="action:bulk read">allow</action>
</actions>
</group>
</groups>
</profile>
<profile id="117" _delta="define">
<name>SuperUser</name>
<description>This profile allows all actions which are not Administrator restricted.</description>

View File

@@ -1036,45 +1036,45 @@ The hyperlink is displayed in the tooltip appearing on the “Lock” symbol of
'Class:SynchroAttLinkSet/Attribute:row_separator' => 'Rows separator',
'Class:SynchroAttLinkSet/Attribute:attribute_separator' => 'Attributes separator',
'Class:SynchroLog' => 'Synchro Log',
'Class:SynchroLog/Attribute:sync_source_id' => 'Synchro Data Source',
'Class:SynchroLog/Attribute:start_date' => 'Start Date',
'Class:SynchroLog/Attribute:end_date' => 'End Date',
'Class:SynchroLog/Attribute:status' => 'Status',
'Class:SynchroLog/Attribute:status/Value:completed' => 'Completed',
'Class:SynchroLog/Attribute:status/Value:error' => 'Error',
'Class:SynchroLog/Attribute:status/Value:running' => 'Still Running',
'Class:SynchroLog/Attribute:stats_nb_replica_seen' => 'Nb replica seen',
'Class:SynchroLog/Attribute:stats_nb_replica_total' => 'Nb replica total',
'Class:SynchroLog/Attribute:stats_nb_obj_deleted' => 'Nb objects deleted',
'Class:SynchroLog/Attribute:stats_nb_obj_deleted_errors' => 'Nb of errors while deleting',
'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted' => 'Nb objects obsoleted',
'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted_errors' => 'Nb of errors while obsoleting',
'Class:SynchroLog/Attribute:stats_nb_obj_created' => 'Nb objects created',
'Class:SynchroLog/Attribute:stats_nb_obj_created_errors' => 'Nb or errors while creating',
'Class:SynchroLog/Attribute:stats_nb_obj_updated' => 'Nb objects updated',
'Class:SynchroLog/Attribute:stats_nb_obj_updated_errors' => 'Nb errors while updating',
'Class:SynchroLog/Attribute:stats_nb_replica_reconciled_errors' => 'Nb of errors during reconciliation',
'Class:SynchroLog/Attribute:sync_source_id' => 'Synchro Data Source',
'Class:SynchroLog/Attribute:start_date' => 'Start Date',
'Class:SynchroLog/Attribute:end_date' => 'End Date',
'Class:SynchroLog/Attribute:status' => 'Status',
'Class:SynchroLog/Attribute:status/Value:completed' => 'Completed',
'Class:SynchroLog/Attribute:status/Value:error' => 'Error',
'Class:SynchroLog/Attribute:status/Value:running' => 'Still Running',
'Class:SynchroLog/Attribute:stats_nb_replica_seen' => 'Nb replica seen',
'Class:SynchroLog/Attribute:stats_nb_replica_total' => 'Nb replica total',
'Class:SynchroLog/Attribute:stats_nb_obj_deleted' => 'Nb objects deleted',
'Class:SynchroLog/Attribute:stats_nb_obj_deleted_errors' => 'Nb of errors while deleting',
'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted' => 'Nb objects obsoleted',
'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted_errors' => 'Nb of errors while obsoleting',
'Class:SynchroLog/Attribute:stats_nb_obj_created' => 'Nb objects created',
'Class:SynchroLog/Attribute:stats_nb_obj_created_errors' => 'Nb or errors while creating',
'Class:SynchroLog/Attribute:stats_nb_obj_updated' => 'Nb objects updated',
'Class:SynchroLog/Attribute:stats_nb_obj_updated_errors' => 'Nb errors while updating',
'Class:SynchroLog/Attribute:stats_nb_replica_reconciled_errors' => 'Nb of errors during reconciliation',
'Class:SynchroLog/Attribute:stats_nb_replica_disappeared_no_action' => 'Nb replica disappeared',
'Class:SynchroLog/Attribute:stats_nb_obj_new_updated' => 'Nb objects updated',
'Class:SynchroLog/Attribute:stats_nb_obj_new_unchanged' => 'Nb objects unchanged',
'Class:SynchroLog/Attribute:last_error' => 'Last error',
'Class:SynchroLog/Attribute:traces' => 'Traces',
'Class:SynchroReplica' => 'Synchro Replica',
'Class:SynchroReplica/Attribute:sync_source_id' => 'Synchro Data Source',
'Class:SynchroReplica/Attribute:dest_id' => 'Destination object (ID)',
'Class:SynchroReplica/Attribute:dest_class' => 'Destination type',
'Class:SynchroReplica/Attribute:status_last_seen' => 'Last seen',
'Class:SynchroReplica/Attribute:status' => 'Status',
'Class:SynchroReplica/Attribute:status/Value:modified' => 'Modified',
'Class:SynchroReplica/Attribute:status/Value:new' => 'New',
'Class:SynchroReplica/Attribute:status/Value:obsolete' => 'Obsolete',
'Class:SynchroReplica/Attribute:status/Value:orphan' => 'Orphan',
'Class:SynchroReplica/Attribute:status/Value:synchronized' => 'Synchronized',
'Class:SynchroReplica/Attribute:status_dest_creator' => 'Object Created ?',
'Class:SynchroReplica/Attribute:status_last_error' => 'Last Error',
'Class:SynchroReplica/Attribute:status_last_warning' => 'Warnings',
'Class:SynchroReplica/Attribute:info_creation_date' => 'Creation Date',
'Class:SynchroReplica/Attribute:info_last_modified' => 'Last Modified Date',
'Class:SynchroLog/Attribute:stats_nb_obj_new_updated' => 'Nb objects updated',
'Class:SynchroLog/Attribute:stats_nb_obj_new_unchanged' => 'Nb objects unchanged',
'Class:SynchroLog/Attribute:last_error' => 'Last error',
'Class:SynchroLog/Attribute:traces' => 'Traces',
'Class:SynchroReplica' => 'Synchro Replica',
'Class:SynchroReplica/Attribute:sync_source_id' => 'Synchro Data Source',
'Class:SynchroReplica/Attribute:dest_id' => 'Destination object (ID)',
'Class:SynchroReplica/Attribute:dest_class' => 'Destination type',
'Class:SynchroReplica/Attribute:status_last_seen' => 'Last seen',
'Class:SynchroReplica/Attribute:status' => 'Status',
'Class:SynchroReplica/Attribute:status/Value:modified' => 'Modified',
'Class:SynchroReplica/Attribute:status/Value:new' => 'New',
'Class:SynchroReplica/Attribute:status/Value:obsolete' => 'Obsolete',
'Class:SynchroReplica/Attribute:status/Value:orphan' => 'Orphan',
'Class:SynchroReplica/Attribute:status/Value:synchronized' => 'Synchronized',
'Class:SynchroReplica/Attribute:status_dest_creator' => 'Object Created ?',
'Class:SynchroReplica/Attribute:status_last_error' => 'Last Error',
'Class:SynchroReplica/Attribute:status_last_warning' => 'Warnings',
'Class:SynchroReplica/Attribute:info_creation_date' => 'Creation Date',
'Class:SynchroReplica/Attribute:info_last_modified' => 'Last Modified Date',
'Class:SynchroReplica/Action:delete+' => 'delete replica',
'Class:SynchroReplica/Action:unlink' => 'Unlink',
'Class:SynchroReplica/Action:unlink+' => 'Unlink replica with destination object',
@@ -1109,26 +1109,26 @@ The hyperlink is displayed in the tooltip appearing on the “Lock” symbol of
'UI:DenyDeleteAllTabTitle' => 'Deny delete of objects linked to Synchro Replica',
'UI:DenyDeleteAllPageTitle' => 'Deny delete of objects linked to Synchro Replica',
'Class:appUserPreferences' => 'User Preferences',
'Class:appUserPreferences/Attribute:userid' => 'User',
'Class:appUserPreferences' => 'User Preferences',
'Class:appUserPreferences/Attribute:userid' => 'User',
'Class:appUserPreferences/Attribute:preferences' => 'Prefs',
'Core:ExecProcess:Code1' => 'Wrong command or command finished with errors (e.g. wrong script name)',
'Core:ExecProcess:Code255' => 'PHP Error (parsing, or runtime)',
'Core:ExecProcess:Code1' => 'Wrong command or command finished with errors (e.g. wrong script name)',
'Core:ExecProcess:Code255' => 'PHP Error (parsing, or runtime)',
// Attribute Duration
'Core:Duration_Seconds' => '%1$ds',
'Core:Duration_Minutes_Seconds' => '%1$dmin %2$ds',
'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds',
'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sd %2$dh %3$dmin %4$ds',
'Core:Duration_Seconds' => '%1$ds',
'Core:Duration_Minutes_Seconds' => '%1$dmin %2$ds',
'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds',
'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sd %2$dh %3$dmin %4$ds',
// Explain working time computing
'Core:ExplainWTC:ElapsedTime' => 'Time elapsed (stored as "%1$s")',
'Core:ExplainWTC:StopWatch-TimeSpent' => 'Time spent for "%1$s"',
'Core:ExplainWTC:StopWatch-Deadline' => 'Deadline for "%1$s" at %2$d%%',
'Core:ExplainWTC:ElapsedTime' => 'Time elapsed (stored as "%1$s")',
'Core:ExplainWTC:StopWatch-TimeSpent' => 'Time spent for "%1$s"',
'Core:ExplainWTC:StopWatch-Deadline' => 'Deadline for "%1$s" at %2$d%%',
// Bulk export
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter "%1$s"',
'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter "query". There is no Query Phrasebook corresponding to the id: "%1$s".',
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter "%1$s"',
'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter "query". There is no Query Phrasebook corresponding to the id: "%1$s".',
'Core:BulkExport:ExportFormatPrompt' => 'Export format:',
'Core:BulkExportOf_Class' => '%1$s Export',
'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s',

View File

@@ -14,6 +14,8 @@ require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
*/
class iTopExtensionsMap
{
private static array $aInstancesByEnvironment = [];
/**
* The list of all discovered extensions
* @var array $aExtensions
@@ -30,25 +32,36 @@ class iTopExtensionsMap
* The list of directories browsed using the ReadDir method when building the map
* @var string[]
*/
protected $aScannedDirs;
protected array $aScannedDirs;
/** @var bool $bHasXmlInstallationFile : false when legacy 1.x package with no installation.xml */
protected $bHasXmlInstallationFile = true;
//extension dirs apart from package
protected array $aExtraDirs = [];
/**
* @throws \Exception
*/
public static function GetExtensionsMap(string $sFromEnvironment = ITOP_DEFAULT_ENV, ?string $sAppRootForTests = null): iTopExtensionsMap
{
if (!is_null($sAppRootForTests)) {
return new iTopExtensionsMap($sFromEnvironment, $sAppRootForTests);
}
if (!isset(self::$aInstancesByEnvironment[$sFromEnvironment])) {
self::$aInstancesByEnvironment[$sFromEnvironment] = new iTopExtensionsMap($sFromEnvironment);
}
return self::$aInstancesByEnvironment[$sFromEnvironment];
}
/**
* The list of all discovered extensions
*
* @param string $sFromEnvironment The environment to scan
* @param array $aExtraDirs extensions dir to scan
* @param array $aExtraDirs extensions dir to scan
* @param string|null $sAppRootForTests
* @param string|null $sAppRootForTests
*
* @return void
* @throws \Exception
*/
public function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [], ?string $sAppRootForTests = null)
private function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, ?string $sAppRootForTests = null)
{
$this->aExtensions = [];
$this->aExtensionsByCode = [];
@@ -56,18 +69,6 @@ class iTopExtensionsMap
$sAppRoot = $sAppRootForTests ?? APPROOT;
$this->ScanDisk($sFromEnvironment, $sAppRoot);
$this->aExtraDirs = $aExtraDirs;
if (is_dir($sAppRoot.'extensions')) {
$this->aExtraDirs [] = $sAppRoot.'extensions';
}
if (is_dir($sAppRoot.'data/'.$sFromEnvironment.'-modules')) {
$this->aExtraDirs [] = $sAppRoot.'data/'.$sFromEnvironment.'-modules';
}
foreach ($aExtraDirs as $sDir) {
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
}
$this->CheckDependencies($sAppRoot);
}
@@ -83,13 +84,15 @@ class iTopExtensionsMap
if (!$this->ReadInstallationWizard($sAppRoot.'/datamodels/2.x')) {
$this->bHasXmlInstallationFile = false;
//no installation xml found in 2.x: let's read all extensions in 2.x first
if (!$this->ReadDir($sAppRoot.'datamodels/2.x', iTopExtension::SOURCE_WIZARD)) {
if (!$this->ReadDir($sAppRoot.'datamodels/2.x', iTopExtension::SOURCE_WIZARD, bIsRootDir: true)) {
//nothing found in 2.x : fallback read in 1.x (flat structure)
$this->ReadDir($sAppRoot.'datamodels/1.x', iTopExtension::SOURCE_WIZARD);
$this->ReadDir($sAppRoot.'datamodels/1.x', iTopExtension::SOURCE_WIZARD, bIsRootDir: true);
}
} else {
$this->aScannedDirs[] = $sAppRoot.'datamodels/2.x';
}
$this->ReadDir($sAppRoot.'extensions', iTopExtension::SOURCE_MANUAL);
$this->ReadDir($sAppRoot.'data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE);
$this->ReadDir($sAppRoot.'extensions', iTopExtension::SOURCE_MANUAL, bIsRootDir: true);
$this->ReadDir($sAppRoot.'data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE, bIsRootDir: true);
}
/**
@@ -274,14 +277,14 @@ class iTopExtensionsMap
*
* @return boolean false if we cannot open dir
*/
protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null)
protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null, bool $bIsRootDir = false)
{
if (!is_readable($sSearchDir)) {
return false;
}
$hDir = opendir($sSearchDir);
if ($hDir !== false) {
if ($sParentExtensionId == null) {
if ($bIsRootDir) {
// We're not recursing, let's add the directory to the list of scanned dirs
$this->aScannedDirs[] = $sSearchDir;
}
@@ -478,16 +481,8 @@ class iTopExtensionsMap
*/
protected function CheckDependencies(string $sAppRoot)
{
$aSearchDirs = [];
if (is_dir($sAppRoot.'/datamodels/2.x')) {
$aSearchDirs[] = $sAppRoot.'/datamodels/2.x';
} elseif (is_dir($sAppRoot.'/datamodels/1.x')) {
$aSearchDirs[] = $sAppRoot.'/datamodels/1.x';
}
$aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs);
try {
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true);
ModuleDiscovery::GetModulesOrderedByDependencies($this->aScannedDirs, true);
} catch (MissingDependencyException $e) {
// Some modules have missing dependencies
// Let's check what is the impact at the "extensions" level
@@ -536,7 +531,7 @@ class iTopExtensionsMap
public function GetAllExtensionsToDisplayInSetup(bool $bKeepExtensionsHavingMissingDependencies = false, bool $bRemoteExtensionsShouldBeMandatory = true): array
{
// all extensions are loaded at first screen when no installation xml: flat display
//otherwhile wizard screen displays choice screens along extension tree (cf installation.xml)
// otherwhile wizard screen displays choice screens along extension tree (cf installation.xml)
$aRes = [];
foreach ($this->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
/** @var \iTopExtension $oExtension */
@@ -596,7 +591,7 @@ class iTopExtensionsMap
{
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
if (!is_null($oExtension)) {
$oExtension->bMarkedAsChosen = $bMark;
$oExtension->MarkAsChosen($bMark);
}
}
@@ -609,7 +604,7 @@ class iTopExtensionsMap
{
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
if (!is_null($oExtension)) {
return $oExtension->bMarkedAsChosen;
return $oExtension->IsMarkedAsChosen();
}
return false;
@@ -636,8 +631,9 @@ class iTopExtensionsMap
public function GetChoices()
{
$aResult = [];
/** @var \iTopExtension $oExtension */
foreach ($this->aExtensions as $oExtension) {
if ($oExtension->bMarkedAsChosen) {
if ($oExtension->IsMarkedAsChosen()) {
$aResult[] = $oExtension;
}
}
@@ -745,11 +741,6 @@ class iTopExtensionsMap
return array_merge($aDbChoices, $aAddedExtensions);
}
public function GetExtraDirs(): array
{
return $this->aExtraDirs;
}
/**
* Tells if the given module name is "chosen" since it is part of a "chosen" extension (in the specified source dir)
* @param string $sModuleNameToFind
@@ -759,13 +750,17 @@ class iTopExtensionsMap
public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSourceOnly = iTopExtension::SOURCE_REMOTE)
{
foreach ($this->GetAllExtensions() as $oExtension) {
if (($oExtension->sSource == $sInSourceOnly) &&
($oExtension->bMarkedAsChosen == true) &&
if ($oExtension->IsMarkedAsChosen() &&
(array_key_exists($sModuleNameToFind, $oExtension->aModuleVersion))) {
return true;
}
}
return false;
}
public function GetScannedModulesRootDirs(): array
{
return $this->aScannedDirs;
}
}

View File

@@ -2,22 +2,24 @@
namespace Combodo\iTop\Setup\FeatureRemoval;
use iTopExtensionsMap;
use Config;
use RunTimeEnvironment;
use SetupUtils;
class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
{
protected array $aExtensionsToRemoveByCode;
protected array $aExtensionCodesToAddByCode;
/**
* Toolset for building a run-time environment
*
* @param string $sSourceEnv: environment from which setup is inspired to simulate extension removal and usee CompileFrom...
*/
public function __construct($sSourceEnv = ITOP_DEFAULT_ENV, array $aExtensionCodesToRemove = [])
public function __construct($sSourceEnv = ITOP_DEFAULT_ENV, array $aExtensionCodesToAdd = [], array $aExtensionCodesToRemove = [])
{
parent::__construct($sSourceEnv, false);
$this->aExtensionCodesToAddByCode = $aExtensionCodesToAdd;
$this->aExtensionsToRemoveByCode = $aExtensionCodesToRemove;
$this->Prepare($sSourceEnv, $this->sBuildEnv);
}
@@ -34,13 +36,18 @@ class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
SetupUtils::copydir(APPROOT."/data/$sSourceEnv-modules", APPROOT."/data/$sBuildEnv-modules");
SetupUtils::copydir(APPROOT."/conf/$sSourceEnv", APPROOT."/conf/$sBuildEnv");
$this->DeclareExtensionAsRemoved($this->aExtensionsToRemoveByCode);
}
$oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
$sSourceDir = $oSourceConfig->Get('source_dir');
list($aExtraDirs, ) = $this->GetDirsToCompile($sSourceDir, $sSourceEnv);
private function DeclareExtensionAsRemoved(array $aExtensionCodes): void
{
$oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv);
$oExtensionsMap->DeclareExtensionAsRemoved($aExtensionCodes);
$this->InitExtensionMap($aExtraDirs, $oSourceConfig);
$this->GetExtensionMap()->DeclareExtensionAsRemoved($this->aExtensionsToRemoveByCode);
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
if (array_key_exists($oExtension->sCode, $this->aExtensionCodesToAddByCode)) {
$oExtension->MarkAsChosen();
}
}
}
public function Cleanup(): void

View File

@@ -17,6 +17,11 @@ class SetupAudit extends AbstractSetupAudit
$this->sEnvAfter = $sEnvAfter ?? "$sEnvBefore-build";
}
public function GetEnvAfter(): string
{
return $this->sEnvAfter;
}
public function ComputeClasses(): void
{
if ($this->bClassesInitialized) {

View File

@@ -1,8 +1,5 @@
<?php
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
require_once(APPROOT.'/setup/parameters.class.inc.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
require_once(APPROOT.'/setup/modulediscovery.class.inc.php');
@@ -60,7 +57,7 @@ class iTopExtension
/**
* @var bool
*/
public $bMarkedAsChosen;
private $bMarkedAsChosen;
/**
* If null, check if at least one module cannot be uninstalled
* @var bool|null
@@ -183,4 +180,14 @@ class iTopExtension
{
return $this->sSource === self::SOURCE_REMOTE;
}
public function IsMarkedAsChosen(): bool
{
return $this->bMarkedAsChosen;
}
public function MarkAsChosen(bool $bMarkedAsChosen = true): void
{
$this->bMarkedAsChosen = $bMarkedAsChosen;
}
}

View File

@@ -68,7 +68,12 @@ WHERE
parent_id='$iRootId'
OR id='$iRootId'
SQL;
return CMDBSource::QueryToArray($sSQL);
try {
return CMDBSource::QueryToArray($sSQL);
} catch (MySQLException $e) {
SetupLog::Exception(__METHOD__, $e);
throw $e;
}
}
private function GetTableWithPrefix(Config $oConfig)

View File

@@ -71,15 +71,37 @@ class RunTimeEnvironment
return $this->oExtensionsMap;
}
public function InitExtensionMap($aExtraDirs, $oSourceConfig)
protected function GetDirsToCompile(string $sSourceDir, string $sSourceEnv): array
{
// Actually read the modules available for the build environment,
// but get the selection from the source environment and finally
// mark as (automatically) chosen all the "remote" modules present in the
// build environment (data/<build-env>-modules)
// The actual choices will be recorded by RecordInstallation below
$this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv, $aExtraDirs);
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
$sSourceDirFull = APPROOT.$sSourceDir;
if (!is_dir($sSourceDirFull)) {
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
}
$aDirsToCompile = [$sSourceDirFull];
if (is_dir(APPROOT.'extensions')) {
$aDirsToCompile[] = APPROOT.'extensions';
}
$sExtraDir = utils::GetDataPath().$this->sBuildEnv.'-modules/';
if (is_dir($sExtraDir)) {
$aDirsToCompile[] = $sExtraDir;
}
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
return [$aExtraDirs, $aDirsToCompile];
}
public function InitExtensionMap(array $aExtraDirs, Config $oSourceConfig)
{
if (is_null($this->oExtensionsMap)) {
// Actually read the modules available for the build environment,
// but get the selection from the source environment and finally
// mark as (automatically) chosen all the "remote" modules present in the
// build environment (data/<build-env>-modules)
// The actual choices will be recorded by RecordInstallation below
$this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->sBuildEnv);
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
}
}
/**
@@ -168,7 +190,7 @@ class RunTimeEnvironment
MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false, $this->sBuildEnv);
if ($this->oExtensionsMap === null) {
$this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv);
$this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->sBuildEnv);
}
}
@@ -444,32 +466,12 @@ class RunTimeEnvironment
/**
* Get the installed modules (only the installed ones)
* @return \MFModule[]
*/
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir): array
{
$sSourceDirFull = APPROOT.$sSourceDir;
if (!is_dir($sSourceDirFull)) {
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
}
$aDirsToCompile = [$sSourceDirFull];
if (is_dir(APPROOT.'extensions')) {
$aDirsToCompile[] = APPROOT.'extensions';
}
$sExtraDir = utils::GetDataPath().$this->sBuildEnv.'-modules/';
if (is_dir($sExtraDir)) {
$aDirsToCompile[] = $sExtraDir;
}
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
list($aExtraDirs, $aDirsToCompile) = $this->GetDirsToCompile($sSourceDir, $sSourceEnv);
$oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
// Actually read the modules available for the build environment,
// but get the selection from the source environment and finally
// mark as (automatically) chosen all the "remote" modules present in the
// build environment (data/<build-env>-modules)
// The actual choices will be recorded by RecordInstallation below
$this->InitExtensionMap($aExtraDirs, $oSourceConfig);
$this->GetExtensionMap()->LoadChoicesFromDatabase($oSourceConfig);
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
@@ -477,12 +479,10 @@ class RunTimeEnvironment
$this->GetExtensionMap()->MarkAsChosen($oExtension->sCode);
}
}
$aModulesToLoad = $this->GetModulesToLoad($this->sFinalEnv, $aDirsToCompile);
$aAvailableModules = $this->AnalyzeInstallation($oSourceConfig, $aDirsToCompile, true, $aModulesToLoad);
// Do load the required modules
//
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
$aRet = [];
@@ -1032,7 +1032,8 @@ class RunTimeEnvironment
@chmod($sFinalConfig, 0770); // In case it exists: RWX for owner and group, nothing for others
$this->CommitFile($sBuildConfig, $sFinalConfig);
@chmod($sFinalConfig, 0440); // Read-only for owner and group, nothing for others
@rmdir(dirname($sBuildConfig)); // Cleanup the temporary build dir if empty
SetupUtils::rrmdir(dirname($sBuildConfig)); // Cleanup the temporary build dir if empty
if (! isset($_SESSION)) {
//used in all UI setups (not unattended)
@@ -1419,8 +1420,6 @@ class RunTimeEnvironment
* @param array $aSelectedExtensionCodes
* @param array $aRemovedExtensionCodes
* @param array $aSelectedModules
* @param string $sSourceDir
* @param string $sExtensionDir
* @param boolean $bUseSymbolicLinks
*
* @return void
@@ -1428,33 +1427,13 @@ class RunTimeEnvironment
* @throws \CoreException
*
*/
public function DoCompile(array $aSelectedExtensionCodes, array $aRemovedExtensionCodes, array $aSelectedModules, string $sSourceDir, string $sExtensionDir, bool $bUseSymbolicLinks = false): void
public function DoCompile(array $aSelectedExtensionCodes, array $aRemovedExtensionCodes, array $aSelectedModules, bool $bUseSymbolicLinks = false): void
{
SetupLog::Info('Compiling data model.');
$sEnvironment = $this->sBuildEnv;
$sBuildPath = $this->GetBuildDir();
$sSourcePath = APPROOT.$sSourceDir;
$aDirsToScan = [$sSourcePath];
$sExtensionsPath = APPROOT.$sExtensionDir;
if (is_dir($sExtensionsPath)) {
// if the extensions dir exists, scan it for additional modules as well
$aDirsToScan[] = $sExtensionsPath;
}
$sExtraPath = APPROOT.'/data/'.$sEnvironment.'-modules/';
if (is_dir($sExtraPath)) {
// if the extra dir exists, scan it for additional modules as well
$aDirsToScan[] = $sExtraPath;
}
if (!is_dir($sSourcePath)) {
$sErrorMessage = "Failed to find the source directory '$sSourcePath', please check the rights of the web server";
$e = new CoreException($sErrorMessage);
IssueLog::Exception($sErrorMessage, $e);
throw $e;
}
if (!is_dir($sBuildPath)) {
if (!mkdir($sBuildPath)) {
$sErrorMessage = "Failed to create directory '$sBuildPath', please check the rights of the web server";
@@ -1471,7 +1450,7 @@ class RunTimeEnvironment
SetupUtils::tidydir($sBuildPath);
}
$oExtensionsMap = new iTopExtensionsMap($this->GetFinalEnv(), $aDirsToScan);
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->GetFinalEnv());
// Removed modules are stored as static for FindModules()
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensionCodes);
@@ -1481,7 +1460,7 @@ class RunTimeEnvironment
$aNoCodeExtensionLabelsThatBreakSetup = [];
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) {
$oExtension->bMarkedAsChosen = true;
$oExtension->MarkAsChosen();
}
if (empty($oExtension->sCode)) {
@@ -1493,7 +1472,7 @@ class RunTimeEnvironment
$aNoCodeExtensionSourceDirs [$sExtensionLabel] = $oExtension->sSourceDir;
}
if ($oExtension->bMarkedAsChosen) {
if ($oExtension->IsMarkedAsChosen()) {
$aNoCodeExtensionLabelsThatBreakSetup[] = $sExtensionLabel;
$bSetupFailure = true;
}
@@ -1511,7 +1490,7 @@ class RunTimeEnvironment
}
}
$oFactory = new ModelFactory($aDirsToScan);
$oFactory = new ModelFactory($oExtensionsMap->GetScannedModulesRootDirs());
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
$oFactory->LoadModule($oDictModule);
@@ -1700,21 +1679,29 @@ class RunTimeEnvironment
}
$aExtensionDirs = [];
$aFromSelectedExtensionModules = [];
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
if ($oExtension->bMarkedAsChosen && is_dir($oExtension->sSourceDir)) {
if ($oExtension->IsMarkedAsChosen() && is_dir($oExtension->sSourceDir)) {
$aExtensionDirs [] = $oExtension->sSourceDir;
$aFromSelectedExtensionModules = array_merge($aFromSelectedExtensionModules, $oExtension->aModules);
}
}
SetupLog::Info(__METHOD__, null, ['ext_dirs' => $aExtensionDirs]);
$aModuleIdsToLoad = InstallationChoicesToModuleConverter::GetInstance()->GetModules($aChoices, $aSearchDirs, $sInstallFilePath, $aExtensionDirs);
$aModulesToLoad = [];
foreach ($aModuleIdsToLoad as $sModuleId) {
$oModule = new Module($sModuleId);
$sModuleName = $oModule->GetModuleName();
$aModulesToLoad[] = $sModuleName;
}
foreach ($aFromSelectedExtensionModules as $sModuleName) {
if (! in_array($sModuleName, $aModulesToLoad)) {
$aModulesToLoad[] = $sModuleName;
}
}
return $aModulesToLoad;
}

View File

@@ -74,8 +74,6 @@ class DataAuditSequencer extends StepSequencer
$aSelectedExtensionCodes,
$aRemovedExtensionCodes,
$aSelectedModules,
$sSourceDir,
$sExtensionDir,
$bUseSymbolicLinks
);
return $this->ComputeNextStep($sStep);

View File

@@ -1602,7 +1602,7 @@ JS
}
$oProductionEnv = new RunTimeEnvironment($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV));
$aRemovedExtensionCodes = json_decode($oWizard->GetParameter('removed_extensions'), true) ?? [];
$oExtensionsMap = new iTopExtensionsMap($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV), $aDirsToScan);
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV));
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensionCodes);
$aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $aDirsToScan, $bAbortOnMissingDependency, $aModulesToLoad);

View File

@@ -79,7 +79,7 @@ class InstallationFileService
public function GetItopExtensionsMap(): iTopExtensionsMap
{
if (is_null($this->oItopExtensionsMap)) {
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment);
$this->oItopExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->sTargetEnvironment);
}
return $this->oItopExtensionsMap;
}

View File

@@ -13,10 +13,10 @@
<target_env>production</target_env>
<workspace_dir></workspace_dir>
<database>
<server>localhost</server>
<user>iTop</user>
<pwd>blob99</pwd>
<name>configcomments</name>
<server></server>
<user></user>
<pwd></pwd>
<name></name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
@@ -24,9 +24,9 @@
<url></url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user>admin</user>
<pwd>admin</pwd>
<language>FR FR</language>
<user></user>
<pwd></pwd>
<language></language>
</admin_account>
<language></language>
<selected_modules type="array">

View File

@@ -55,7 +55,7 @@ class WizStepModulesChoice extends AbstractWizStepInstall
{
parent::__construct($oWizard, $sCurrentState);
$this->bChoicesFromDatabase = false;
$this->oExtensionsMap = new iTopExtensionsMap();
$this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$sPreviousSourceDir = $this->oWizard->GetParameter('previous_version_dir', '');
$sConfigPath = null;
if (($sPreviousSourceDir !== '') && is_readable($sPreviousSourceDir.'/conf/'.ITOP_DEFAULT_ENV.'/config-itop.php')) {
@@ -81,7 +81,6 @@ class WizStepModulesChoice extends AbstractWizStepInstall
// Sanity check (not stopper, to let developers go further...)
try {
$aModulesToLoad = json_decode($oWizard->GetParameter('selected_modules'), true) ?? null;
SetupLog::Error(__METHOD__, null, [$aModulesToLoad]);
$this->aAnalyzeInstallationModules = SetupUtils::AnalyzeInstallation($this->oWizard, true, $aModulesToLoad, $this->oConfig);
} catch (MissingDependencyException $e) {
$this->oMissingDependencyException = $e;

View File

@@ -7,7 +7,6 @@
namespace Combodo\iTop\Controller;
use Combodo\iTop\Application\WebPage\AjaxPage;
use ApplicationContext;
use ApplicationMenu;
use AttributeLinkedSet;
@@ -21,7 +20,8 @@ use CMDBObjectSet;
use CMDBSource;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectSummary;
use Combodo\iTop\Application\WebPage\AjaxPage;
use Combodo\iTop\Application\WebPage\JsonPage;
use DBObjectSearch;
use DBObjectSet;
use DBSearch;
@@ -34,7 +34,6 @@ use FunctionExpression;
use IssueLog;
use iTopExtension;
use iTopExtensionsMap;
use Combodo\iTop\Application\WebPage\JsonPage;
use LogChannels;
use MetaModel;
use ormSet;
@@ -963,7 +962,7 @@ EOF
$oPage->add('<ul style="margin: 0;">');
require_once(APPROOT.'setup/extensionsmap.class.inc.php');
$oExtensionsMap = new iTopExtensionsMap();
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
$aChoices = $oExtensionsMap->GetChoices();
foreach ($aChoices as $oExtension) {

View File

@@ -87,8 +87,6 @@ abstract class ItopDataTestCase extends ItopTestCase
*/
public const DEFAULT_TEST_ENVIRONMENT = 'production';
public const USE_TRANSACTION = true;
public const CREATE_TEST_ORG = false;
protected static $aURP_Profiles = [
'Administrator' => 1,
'Portal user' => 2,
@@ -102,9 +100,15 @@ abstract class ItopDataTestCase extends ItopTestCase
'Service Manager' => 10,
'Document author' => 11,
'Portal power user' => 12,
'Business partner user' => 40,
'REST Services User' => 1024,
'Configuration ReadOnly' => 5500,
'Ticket ReadOnly' => 5501,
'Service Catalog ReadOnly' => 5502,
];
public const CREATE_TEST_ORG = false;
/**
* This method is called before the first test of this test class is run (in the current process).
*/
@@ -1463,16 +1467,42 @@ abstract class ItopDataTestCase extends ItopTestCase
]);
}
/**
* @description To avoid adding finalclasses parameters to GivenUserInDB
* @param string $sPassword
* @param array $aProfiles Profile names Example: ['Administrator']
* @param bool $bReturnLogin
*
* @return string|int The unique login
* @throws \Exception
*/
protected function GivenTokenUserInDB(array $aProfiles, bool $bReturnLogin = true): string|int
{
$sLogin = 'demo_test_'.uniqid(__CLASS__, true);
$aProfileList = array_map(function ($sProfileId) {
return 'profileid:'.self::$aURP_Profiles[$sProfileId];
}, $aProfiles);
$iUser = $this->GivenObjectInDB('UserToken', [
'login' => $sLogin,
'language' => 'EN US',
'profile_list' => $aProfileList,
]);
return $bReturnLogin ? $sLogin : $iUser;
}
/**
* @param string $sPassword
* @param array $aProfiles Profile names Example: ['Administrator']
* @param string|null $sLogin
* @param string|null $sUserId
* @param bool $bReturnLogin
*
* @return string The unique login
* @throws \Exception
*/
protected function GivenUserInDB(string $sPassword, array $aProfiles, ?string $sLogin = null, ?string &$sUserId = null): string
protected function GivenUserInDB(string $sPassword, array $aProfiles, ?string $sLogin = null, ?string &$sUserId = null, bool $bReturnLogin = true): string
{
if (is_null($sLogin)) {
$sLogin = 'demo_test_'.uniqid(__CLASS__, true);
@@ -1489,7 +1519,7 @@ abstract class ItopDataTestCase extends ItopTestCase
'profile_list' => $aProfileList,
]);
return $sLogin;
return $bReturnLogin ? $sLogin : $sUserId;
}
/**

View File

@@ -108,7 +108,7 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
/**
* @inheritDoc
*/
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir): array
{
\SetupLog::Info(__METHOD__);
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);

View File

@@ -29,11 +29,11 @@ namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use CoreCannotSaveObjectException;
use CoreException;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use DeleteException;
use Dict;
use MetaModel;
use UserLocal;
use UserRights;
@@ -81,6 +81,54 @@ class UserRightsTest extends ItopDataTestCase
return $oUser;
}
/**
* @param array $aProfileIds
* @param array $aShouldBeAllowedToSeeClass
* @param array $aShouldBeAllowedToEditClass
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \DictExceptionUnknownLanguage
* @throws \MySQLException
* @throws \OQLException
* @dataProvider ReadOnlyProvider
*/
public function testReadOnlyUser(array $aProfileIds, array $aShouldBeAllowedToSeeClass, array $aShouldBeAllowedToEditClass): void
{
$oUser = $this->GivenUserWithProfiles('test1', $aProfileIds);
$oUser->DBInsert();
$_SESSION = [];
UserRights::Login($oUser->Get('login'));
$aClassesToTest = ['FunctionalCI', 'Ticket', 'ServiceFamily'];
foreach ($aClassesToTest as $sClass) {
$bShouldBeAllowedToSee = in_array($sClass, $aShouldBeAllowedToSeeClass);
$bIsAllowedReading = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_READ);
$this->assertSame(
$bShouldBeAllowedToSee,
$bIsAllowedReading,
"User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToSee ? "" : "NOT ")."be allowed to see class $sClass"
);
$bShouldBeAllowedToEdit = in_array($sClass, $aShouldBeAllowedToEditClass);
$bIsAllowedEditing = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY);
$this->assertSame(
$bIsAllowedEditing,
$bShouldBeAllowedToEdit,
"User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToEdit ? "" : "NOT ")."be allowed to edit class $sClass"
);
}
}
protected function GivenUserWithProfiles(string $sLogin, array $aProfileIds): DBObject
{
$oProfiles = new \ormLinkSet(\UserLocal::class, 'profile_list', \DBObjectSet::FromScratch(\URP_UserProfile::class));
@@ -433,7 +481,7 @@ class UserRightsTest extends ItopDataTestCase
$oUser = $this->GivenUserWithProfiles('test1', [$iProfileId, 2]);
$this->expectException(CoreCannotSaveObjectException::class);
$this->expectExceptionMessage('Profile "Portal user" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)');
$this->expectExceptionMessage(Dict::Format('Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice', PORTAL_PROFILE_NAME));
$oUser->DBInsert();
}
@@ -572,4 +620,82 @@ class UserRightsTest extends ItopDataTestCase
$oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
static::assertNull($oUser, 'FindUser should return null when the login is unknown');
}
protected function ReadOnlyProvider(): array
{
return [
'CI' => [
'ProfilesId' => [
5500,
],
'ShouldBeAllowedToSeeClasses' => [
'FunctionalCI',
],
'ShouldBeAllowedToEditClasses' => [],
],
'Tickets' => [
'ProfilesId' => [
5501,
],
'ShouldBeAllowedToSeeClasses' => [
'Ticket',
],
'ShouldBeAllowedToEditClasses' => [],
],
'Catalog' => [
'ProfilesId' => [
5502,
],
'ShouldBeAllowedToSeeClasses' => [
'ServiceFamily',
],
'ShouldBeAllowedToEditClasses' => [],
],
'CI and Tickets' => [
'ProfilesId' => [
5500, 5501,
],
'ShouldBeAllowedToSeeClasses' => [
'FunctionalCI', 'Ticket',
],
'ShouldBeAllowedToEditClasses' => [],
],
'CI and Catalog' => [
'ProfilesId' => [
5500, 5502,
],
'ShouldBeAllowedToSeeClasses' => [
'FunctionalCI', 'ServiceFamily',
],
'ShouldBeAllowedToEditClasses' => [],
],
'Tickets and Catalog' => [
'ProfilesId' => [
5501, 5502,
],
'ShouldBeAllowedToSeeClasses' => [
'Ticket', 'ServiceFamily',
],
'ShouldBeAllowedToEditClasses' => [],
],
'Tickets and Catalog + profile Ccnfiguration Manager' => [
'ProfilesId' => [
5501, 5502, 3,
],
'ShouldBeAllowedToSeeClasses' => [
'FunctionalCI', 'Ticket', 'ServiceFamily',
],
'ShouldBeAllowedToEditClasses' => ['FunctionalCI'],
],
'CI, Tickets and Catalog' => [
'ProfilesId' => [
5500, 5501, 5502,
],
'ShouldBeAllowedToSeeClasses' => [
'FunctionalCI', 'Ticket', 'ServiceFamily',
],
'ShouldBeAllowedToEditClasses' => [],
],
];
}
}

View File

@@ -19,7 +19,7 @@ class ExtensionsMapTest extends ItopTestCase
public function testGetAllExtensionsWithPreviouslyInstalledDoesNotCrash()
{
$oExtensionsMap = new iTopExtensionsMap();
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$aExtensions = $oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled();
$this->assertGreaterThan(0, count($aExtensions));
}
@@ -84,7 +84,7 @@ class ExtensionsMapTest extends ItopTestCase
private function GiveExtensionMapWithAllTypeOfExtensions(): iTopExtensionsMap
{
$oExtensionsMap = new iTopExtensionsMap();
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
$this->SetNonPublicProperty($oExtensionsMap, 'aInstalledExtensions', []);
$this->SetNonPublicProperty($oExtensionsMap, 'aExtensions', []);
@@ -153,9 +153,7 @@ class ExtensionsMapTest extends ItopTestCase
public function testiTopExtensionsMapInit()
{
$oiTopExtensionsMap = new iTopExtensionsMap(sAppRootForTests:__DIR__."/ressources");
//file_put_contents(__DIR__.'/ressources/all_extensions_from_datamodels.json', json_encode($this->SerializeExtensionMap($oiTopExtensionsMap), JSON_PRETTY_PRINT));
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(sAppRootForTests: __DIR__."/ressources/");
$sExpected = file_get_contents(__DIR__.'/ressources/all_extensions_from_datamodels.json');
$sExpected = str_replace('"sVersion": "ITOP_VERSION"', '"sVersion": "'.ITOP_VERSION.'"', $sExpected);
@@ -166,6 +164,19 @@ class ExtensionsMapTest extends ItopTestCase
$this->assertEquals($sExpected, $actual);
}
public function testGetScannedModulesRootDirs()
{
$sAppRootForTest = __DIR__."/resources2/";
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(sAppRootForTests: $sAppRootForTest);
$aExpectedDirs = [
"{$sAppRootForTest}datamodels/2.x",
"{$sAppRootForTest}extensions",
"{$sAppRootForTest}data/production-modules",
];
self::assertEquals($aExpectedDirs, $oiTopExtensionsMap->GetScannedModulesRootDirs());
}
public function SerializeExtensionMap(iTopExtensionsMap $oiTopExtensionsMap): array
{
$aRes = [];

View File

@@ -9,30 +9,15 @@ use RunTimeEnvironment;
class RunTimeEnvironmentTest extends ItopTestCase
{
private ?string $sFixtureRootDir = null;
private ?string $sBuildDirToCleanup = null;
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('/setup/runtimeenv.class.inc.php');
}
protected function tearDown(): void
{
if (($this->sFixtureRootDir !== null) && is_dir($this->sFixtureRootDir)) {
self::RecurseRmdir($this->sFixtureRootDir);
}
if (($this->sBuildDirToCleanup !== null) && is_dir($this->sBuildDirToCleanup)) {
self::RecurseRmdir($this->sBuildDirToCleanup);
}
parent::tearDown();
}
public function testDoCompileDoesNotThrowWhenUnselectedExtensionCodeIsMissing(): void
{
[$sToken, $sSourceDirRelative, $sExtensionsDirRelative] = $this->CreateFixtureContext('runtimeenv-missing-code-');
[$sEnvironment, $sExtensionsDirRelative] = $this->CreateFixtureContext('env-missing-code-');
$sInvalidExtensionXml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
@@ -41,20 +26,20 @@ class RunTimeEnvironmentTest extends ItopTestCase
<description>Test extension without code</description>
<version>1.0.0</version>
<mandatory>false</mandatory>
<more_info_url></more_info_url>
<more_info_url/>
</extension>
XML;
file_put_contents(APPROOT.$sExtensionsDirRelative.'/extension.xml', $sInvalidExtensionXml);
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sToken);
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sEnvironment);
//early DOMFormatException to avoid any real compilation
$this->expectException(DOMFormatException::class);
$oRunTimeEnvironment->DoCompile([], [], [], $sSourceDirRelative, $sExtensionsDirRelative);
$oRunTimeEnvironment->DoCompile([], [], []);
}
public function testDoCompileThrowsWhenSelectedExtensionCodeIsMissing(): void
{
[$sToken, $sSourceDirRelative, $sExtensionsDirRelative] = $this->CreateFixtureContext('runtimeenv-missing-code-');
[$sEnvironment, $sExtensionsDirRelative] = $this->CreateFixtureContext('env-missing-code-');
$sInvalidExtensionXml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
@@ -63,21 +48,21 @@ XML;
<description>Test extension without code</description>
<version>1.0.0</version>
<mandatory>false</mandatory>
<more_info_url></more_info_url>
<more_info_url/>
</extension>
XML;
file_put_contents(APPROOT.$sExtensionsDirRelative.'/extension.xml', $sInvalidExtensionXml);
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sToken);
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sEnvironment);
$this->expectException(Exception::class);
$this->expectExceptionMessage("Selected extension(s) cannot be installed: Missing extension code (Broken extension)");
$oRunTimeEnvironment->DoCompile([""], [], [], $sSourceDirRelative, $sExtensionsDirRelative);
$oRunTimeEnvironment->DoCompile([""], [], []);
}
public function testDoCompileThrowsWhenSelectedExtensionCodeAndLabelAreMissing(): void
{
[$sToken, $sSourceDirRelative, $sExtensionsDirRelative] = $this->CreateFixtureContext('runtimeenv-missing-label-');
[$sEnvironment, $sExtensionsDirRelative] = $this->CreateFixtureContext('env-missing-label-');
$sInvalidExtensionXml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
@@ -85,36 +70,34 @@ XML;
<description>Test extension without code and label</description>
<version>1.0.0</version>
<mandatory>false</mandatory>
<more_info_url></more_info_url>
<more_info_url/>
</extension>
XML;
$sExtensionsDirAbsolute = APPROOT.$sExtensionsDirRelative;
file_put_contents($sExtensionsDirAbsolute.'/extension.xml', $sInvalidExtensionXml);
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sToken);
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sEnvironment);
$this->expectException(Exception::class);
$this->expectExceptionMessage("Selected extension(s) cannot be installed: Missing extension code ($sExtensionsDirAbsolute)");
$oRunTimeEnvironment->DoCompile([""], [], [], $sSourceDirRelative, $sExtensionsDirRelative, false);
$oRunTimeEnvironment->DoCompile([""], [], [], false);
}
private function CreateFixtureContext(string $sTokenPrefix): array
private function CreateFixtureContext(string $sEnvPrefix): array
{
$sToken = str_replace('.', '-', uniqid($sTokenPrefix, true));
$this->sFixtureRootDir = APPROOT.'data/'.$sToken;
$sSourceDirRelative = 'data/'.$sToken.'/source';
$sExtensionsDirRelative = 'data/'.$sToken.'/extensions';
$sEnvironment = str_replace('.', '-', uniqid($sEnvPrefix, true));
$sExtensionsDirRelative = 'data/'.$sEnvironment.'-modules';
mkdir(APPROOT.$sSourceDirRelative, 0777, true);
mkdir(APPROOT.$sExtensionsDirRelative, 0777, true);
$this->aFileToClean[] = APPROOT.$sExtensionsDirRelative;
return [$sToken, $sSourceDirRelative, $sExtensionsDirRelative];
return [$sEnvironment, $sExtensionsDirRelative];
}
private function CreateRunTimeEnvironment(string $sToken): RunTimeEnvironment
private function CreateRunTimeEnvironment(string $sEnvironment): RunTimeEnvironment
{
$oRunTimeEnvironment = new RunTimeEnvironment('test-'.$sToken, false);
$this->sBuildDirToCleanup = $oRunTimeEnvironment->GetBuildDir();
$oRunTimeEnvironment = new RunTimeEnvironment($sEnvironment, false);
$this->aFileToClean[] = $oRunTimeEnvironment->GetBuildDir();
return $oRunTimeEnvironment;
}

View File

@@ -9,11 +9,14 @@ use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
use MetaModel;
use SetupUtils;
class SetupAuditTest extends ItopCustomDatamodelTestCase
{
public const ENVT = 'php-unit-extensionremoval-tests';
private ?string $sDirPathToRemoveFromExtensionFolder = null;
public function GetDatamodelDeltaAbsPath(): string
{
//no delta: empty path provided
@@ -44,6 +47,14 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
$this->RequireOnceItopFile('/setup/feature_removal/DryRemovalRuntimeEnvironment.php');
}
protected function tearDown(): void
{
parent::tearDown();
if (!is_null($this->sDirPathToRemoveFromExtensionFolder) && is_dir($this->sDirPathToRemoveFromExtensionFolder)) {
SetupUtils::rrmdir($this->sDirPathToRemoveFromExtensionFolder);
}
}
public function GetTestEnvironment(): string
{
return self::ENVT;
@@ -51,7 +62,11 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
public function testComputeDryRemoval()
{
$oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment($this->GetTestEnvironment(), ['nominal_ext1', 'finalclass_ext2']);
$this->sDirPathToRemoveFromExtensionFolder = APPROOT.'/extensions/finalclass_ext3';
SetupUtils::copydir(__DIR__.'/other_features/finalclass_ext3', $this->sDirPathToRemoveFromExtensionFolder);
clearstatcache();
$oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment($this->GetTestEnvironment(), ['finalclass_ext3' => 'Ext For Test3'], ['nominal_ext1', 'finalclass_ext2']);
$oDryRemovalRuntimeEnvt->CompileFrom($this->GetTestEnvironment());
$oSetupAudit = new SetupAudit($this->GetTestEnvironment());
@@ -67,6 +82,19 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
"FinalClassFeature2Module1MyFinalClassFromLocation" => 0,
];
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->RunDataAudit());
$aClassesAfter = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($oSetupAudit->GetEnvAfter());
$expected = [
"FinalClassFeature3Module1MyClass",
"FinalClassFeature3Module1MyFinalClassFromLocation",
];
foreach ($expected as $sAddedClass) {
$this->assertContains(
$sAddedClass,
$aClassesAfter,
"After DryRemoval compilation DM should contain classes coming from finalclass_ext3 extension"
);
}
}
public function testGetRemovedClassesFromSetupWizard()

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension format="1.0">
<extension_code>finalclass_ext3</extension_code>
<company>Combodo SARL</company>
<author><![CDATA[Odain]]></author>
<label><![CDATA[Ext For Test3]]></label>
<description><![CDATA[Ext For Test]]></description>
<version>6.6.6</version>
<modules type="array">
<module>
<id>finalclass_ext3_module1</id>
<version>tags/6.6.6</version>
</module>
</modules>
<release_date>2023-07-19</release_date>
<version_description><![CDATA[
]]></version_description>
<itop_version_min>3.2.0</itop_version_min>
<status></status>
<mandatory>false</mandatory>
<more_info_url></more_info_url>
</extension>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
<classes>
<class id="FinalClassFeature3Module1MyFinalClassFromLocation" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>FinalClassFeature3Module1MyFinalClassFromLocation</db_table>
<naming>
<attributes>
<attribute id="name2"/>
<attribute id="finalclass"/>
</attributes>
</naming>
<reconciliation>
<attributes>
<attribute id="name2"/>
<attribute id="finalclass"/>
</attributes>
</reconciliation>
<order>
<columns>
<column id="name2" ascending="false"/>
</columns>
</order>
</properties>
<fields>
<field id="name2" xsi:type="AttributeString">
<sql>name2</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
<methods/>
<presentation/>
<parent>Location</parent>
</class>
<class id="FinalClassFeature3Module1MyClass" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>FinalClassFeature3Module1MyClass</db_table>
<naming>
<attributes>
<attribute id="name"/>
</attributes>
</naming>
<reconciliation>
<attributes>
<attribute id="name"/>
</attributes>
</reconciliation>
<order>
<columns>
<column id="name" ascending="false"/>
</columns>
</order>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
<validation_pattern/>
</field>
</fields>
<methods/>
<presentation/>
<parent>cmdbAbstractObject</parent>
</class>
</classes>
<menus/>
<user_rights/>
<module_parameters/>
</itop_design>

View File

@@ -0,0 +1,16 @@
<?php
//// PHP Data Model definition file
//
//// WARNING - WARNING - WARNING
//// DO NOT EDIT THIS FILE (unless you know what you are doing)
////
//// If you use supply a datamodel.xxxx.xml file with your module
//// the this file WILL BE overwritten by the compilation of the
//// module (during the setup) if the datamodel.xxxx.xml file
//// contains the definition of new classes or menus.
////
//// The recommended way to define new classes (for iTop 2.0) is via the XML definition.
//// This file remains in the module's template only for the cases where there is:
//// - either no new class or menu defined in the XML file
//// - or no XML file at all supplied by the module

View File

@@ -0,0 +1,51 @@
<?php
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
//
// iTop module definition file
//
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'finalclass_ext3_module1/6.6.6',
[
// Identification
//
'label' => 'Ext For Test',
'category' => 'business',
// Setup
//
'dependencies' => [
'itop-structure/3.2.0',
],
'mandatory' => false,
'visible' => true,
'installer' => '',
// Components
//
'datamodel' => [
'model.finalclass_ext3_module1.php',
],
'webservice' => [],
'data.struct' => [// add your 'structure' definition XML files here,
],
'data.sample' => [// add your sample data XML files here,
],
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => [// Module specific settings go here, if any
],
]
);

View File

@@ -0,0 +1,243 @@
<?xml version="1.0" encoding="UTF-8"?>
<installation>
<steps type="array">
<step>
<title>Configuration Management options</title>
<description><![CDATA[<h2>The options below allow you to configure the type of elements that are to be managed inside iTop.</h2>]]></description>
<banner>/images/icons/icons8-apps-tab.svg</banner>
<options type="array">
<choice>
<extension_code>itop-config-mgmt-core</extension_code>
<title>Configuration Management Core</title>
<description>All the base objects that are mandatory in the iTop CMDB: Organizations, Locations, Teams, Persons, etc.</description>
<modules type="array">
<module>itop-structure</module>
<module>itop-config-mgmt</module>
<module>itop-attachments</module>
<module>itop-profiles-itil</module>
<module>itop-welcome-itil</module>
<module>itop-tickets</module>
<module>itop-files-information</module>
<module>combodo-db-tools</module>
<module>itop-core-update</module>
<module>itop-hub-connector</module>
<module>itop-oauth-client</module>
<module>combodo-backoffice-darkmoon-theme</module>
<module>combodo-backoffice-fullmoon-high-contrast-theme</module>
<module>combodo-backoffice-fullmoon-protanopia-deuteranopia-theme</module>
<module>combodo-backoffice-fullmoon-tritanopia-theme</module>
<module>itop-themes-compat</module>
<module>combodo-my-account</module>
<module>combodo-my-account-user-info</module>
<module>combodo-oauth2-client</module>
<module>itop-attribute-class-set</module>
<module>itop-attribute-encrypted-password</module>
<module>itop-ui-copypaste</module>
</modules>
<mandatory>true</mandatory>
</choice>
<choice>
<extension_code>itop-config-mgmt-datacenter</extension_code>
<title>Data Center Devices</title>
<description>Manage Data Center devices such as Racks, Enclosures, PDUs, etc.</description>
<modules type="array">
<module>itop-datacenter-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-config-mgmt-end-user</extension_code>
<title>End-User Devices</title>
<description>Manage devices related to end-users: PCs, Phones, Tablets, etc.</description>
<modules type="array">
<module>itop-endusers-devices</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-config-mgmt-storage</extension_code>
<title>Storage Devices</title>
<description>Manage storage devices such as NAS, SAN Switches, Tape Libraries and Tapes, etc.</description>
<modules type="array">
<module>itop-storage-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-config-mgmt-virtualization</extension_code>
<title>Virtualization</title>
<description>Manage Hypervisors, Virtual Machines and Farms.</description>
<modules type="array">
<module>itop-virtualization-mgmt</module>
</modules>
<default>true</default>
<sub_options>
<options type="array">
<choice>
<extension_code>itop-container-mgmt</extension_code>
<title>Containerization</title>
<description><![CDATA[Manage Container Images, Applications and Hosts]]></description>
<modules type="array">
<module>itop-container-mgmt</module>
</modules>
<default>false</default>
</choice>
</options>
</sub_options>
</choice>
</options>
</step>
<step>
<title>Service Management options</title>
<description><![CDATA[<h2>Select the choice that best describes the relationships between the services and the IT infrastructure in your IT environment.</h2>]]></description>
<banner>/images/icons/icons8-services.svg</banner>
<alternatives type="array">
<choice>
<extension_code>itop-service-mgmt-enterprise</extension_code>
<title>Service Management for Enterprises</title>
<description>Select this option if the IT delivers services based on a shared infrastructure. For example if different organizations within your company subscribe to services (like Mail and Print services) delivered by a single shared backend.</description>
<modules type="array">
<module>itop-service-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-service-mgmt-service-provider</extension_code>
<title>Service Management for Service Providers</title>
<description>Select this option if the IT manages the infrastructure of independent customers. This is the most flexible model, since the services can be delivered with a mix of shared and customer specific infrastructure devices.</description>
<modules type="array">
<module>itop-service-mgmt-provider</module>
</modules>
</choice>
</alternatives>
</step>
<step>
<title>Tickets Management options</title>
<description><![CDATA[<h2>Select the type of tickets you want to use in order to respond to user requests and incidents.</h2>]]></description>
<banner>/images/icons/icons8-discussion-forum.svg</banner>
<alternatives type="array">
<choice>
<extension_code>itop-ticket-mgmt-simple-ticket</extension_code>
<title>Simple Ticket Management</title>
<description>Select this option to use one single type of tickets for all kind of requests.</description>
<modules type="array">
<module>itop-request-mgmt</module>
</modules>
<default>true</default>
<sub_options>
<options type="array">
<choice>
<extension_code>itop-ticket-mgmt-simple-ticket-enhanced-portal</extension_code>
<title>Customer Portal</title>
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
<modules type="array">
<module>itop-portal</module>
<module>itop-portal-base</module>
</modules>
<default>true</default>
</choice>
</options>
</sub_options>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-itil</extension_code>
<title>ITIL Compliant Tickets Management</title>
<description>Select this option to have different types of ticket for managing user requests and incidents. Each type of ticket has a specific life cycle and specific fields</description>
<sub_options>
<options type="array">
<choice>
<extension_code>itop-ticket-mgmt-itil-user-request</extension_code>
<title>User Request Management</title>
<description>Manage User Request tickets in iTop</description>
<modules type="array">
<module>itop-request-mgmt-itil</module>
</modules>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-itil-incident</extension_code>
<title>Incident Management</title>
<description>Manage Incidents tickets in iTop</description>
<modules type="array">
<module>itop-incident-mgmt-itil</module>
</modules>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-itil-enhanced-portal</extension_code>
<title>Customer Portal</title>
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
<modules type="array">
<module>itop-portal</module>
<module>itop-portal-base</module>
</modules>
<default>true</default>
</choice>
</options>
</sub_options>
</choice>
<choice>
<extension_code>itop-ticket-mgmt-none</extension_code>
<title>No Tickets Management</title>
<description>Don't manage incidents or user requests in iTop</description>
<modules type="array">
</modules>
</choice>
</alternatives>
</step>
<step>
<title>Change Management options</title>
<description><![CDATA[<h2>Select the type of tickets you want to use in order to manage changes to the IT infrastructure.</h2>]]></description>
<banner>/images/icons/icons8-change.svg</banner>
<alternatives type="array">
<choice>
<extension_code>itop-change-mgmt-simple</extension_code>
<title>Simple Change Management</title>
<description>Select this option to use one type of ticket for all kind of changes.</description>
<modules type="array">
<module>itop-change-mgmt</module>
</modules>
<default>true</default>
</choice>
<choice>
<extension_code>itop-change-mgmt-itil</extension_code>
<title>ITIL Change Management</title>
<description>Select this option to use Normal/Routine/Emergency change tickets.</description>
<modules type="array">
<module>itop-change-mgmt-itil</module>
</modules>
</choice>
<choice>
<extension_code>itop-change-mgmt-none</extension_code>
<title>No Change Management</title>
<description>Don't manage changes in iTop</description>
<modules type="array">
</modules>
</choice>
</alternatives>
</step>
<step>
<title>Additional ITIL tickets</title>
<description><![CDATA[<h2>Pick from the list below the additional ITIL processes that are to be implemented in iTop.</h2>]]></description>
<banner>/images/icons/icons8-important-book.svg</banner>
<options type="array">
<choice>
<!-- Extension code has a typo but fixing it would remove that setup option for all existing iTop -->
<extension_code>itop-kown-error-mgmt</extension_code>
<title>Known Errors Management and FAQ</title>
<description>Select this option to track "Known Errors" and FAQs in iTop.</description>
<modules type="array">
<module>itop-faq-light</module>
<module>itop-knownerror-mgmt</module>
</modules>
</choice>
<choice>
<extension_code>itop-problem-mgmt</extension_code>
<title>Problem Management</title>
<description>Select this option to track "Problems" in iTop.</description>
<modules type="array">
<module>itop-problem-mgmt</module>
</modules>
</choice>
</options>
</step>
</steps>
</installation>