mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-28 04:14:13 +01:00
Compare commits
63 Commits
feature/87
...
feature/88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aac64cc19 | ||
|
|
f668d94bd9 | ||
|
|
00e071228e | ||
|
|
19b7f851f7 | ||
|
|
f9a1b444ab | ||
|
|
3b38dac8c6 | ||
|
|
3d46fe6ef1 | ||
|
|
4dba47798c | ||
|
|
9480c4053d | ||
|
|
b967fb7f20 | ||
|
|
9ea197148c | ||
|
|
49a7f3118d | ||
|
|
195038c941 | ||
|
|
bf80b5dca2 | ||
|
|
09afcb229c | ||
|
|
92f843f676 | ||
|
|
3df4ddc696 | ||
|
|
d552727c55 | ||
|
|
1fe401c102 | ||
|
|
a4b0b6e855 | ||
|
|
9a8d87e2b5 | ||
|
|
545028d68a | ||
|
|
6cb08ba361 | ||
|
|
b3cd79605d | ||
|
|
f9db405343 | ||
|
|
4f1f144c51 | ||
|
|
ef8ade5d20 | ||
|
|
cef4a52081 | ||
|
|
dc9fb2d693 | ||
|
|
e9ffbe5b09 | ||
|
|
9c792a601f | ||
|
|
cda6c1dfa8 | ||
|
|
0b242d872a | ||
|
|
c144c80663 | ||
|
|
d9261b8342 | ||
|
|
df567fb9fe | ||
|
|
028767768c | ||
|
|
11ec80830e | ||
|
|
edf992ef2b | ||
|
|
7c8fb1a51d | ||
|
|
aa27b3601b | ||
|
|
385302c44c | ||
|
|
b563f113d0 | ||
|
|
715d9d3b1c | ||
|
|
7a6c2bc6a4 | ||
|
|
8919184ef9 | ||
|
|
7bfaebe4db | ||
|
|
8e10cf9b72 | ||
|
|
9f3d7d2c36 | ||
|
|
ae980e365d | ||
|
|
a2b01b3ed4 | ||
|
|
9cdc707bc5 | ||
|
|
76178c16b8 | ||
|
|
73f868ac83 | ||
|
|
5a2157ba21 | ||
|
|
03e25a226e | ||
|
|
24048d2b9c | ||
|
|
d8121b563a | ||
|
|
f266f5ff36 | ||
|
|
4a6b129eb8 | ||
|
|
4187f552a9 | ||
|
|
7f7ce0837e | ||
|
|
7df09541ac |
@@ -211,6 +211,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'allowed_login_types' => [
|
||||
'type' => 'string',
|
||||
'description' => 'List of login types allowed (separated by | ): form, external, basic, token',
|
||||
'default' => DEFAULT_ALLOWED_LOGIN_TYPES,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'app_icon_url' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Hyperlink to redirect the user when clicking on the application icon (in the main window, or login/logoff pages)',
|
||||
@@ -2410,6 +2418,7 @@ class Config
|
||||
public function SetAllowedLoginTypes($aAllowedLoginTypes)
|
||||
{
|
||||
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
|
||||
$this->Set('allowed_login_types', implode('|', $aAllowedLoginTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -462,6 +462,19 @@ abstract class MetaModel
|
||||
return call_user_func([$sClass, 'GetClassDescription'], $sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetCreatedIn($sClass)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
|
||||
return self::$m_aClassParams[$sClass]["created_in"] ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
@@ -3145,6 +3158,7 @@ abstract class MetaModel
|
||||
$aMandatParams = [
|
||||
"category" => "group classes by modules defining their visibility in the UI",
|
||||
"key_type" => "autoincrement | string",
|
||||
//"created_in" => "module_name where class is defined",
|
||||
"name_attcode" => "define which attribute is the class name, may be an array of attributes (format specified in the dictionary as 'Class:myclass/Name' => '%1\$s %2\$s...'",
|
||||
"state_attcode" => "define which attribute is representing the state (object lifecycle)",
|
||||
"reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -311,29 +311,35 @@ fieldset {
|
||||
}
|
||||
|
||||
.module-selection-body {
|
||||
overflow: auto;
|
||||
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
|
||||
background-color: #F7FAFC;
|
||||
padding: 10px;
|
||||
|
||||
.wiz-choice:checked ~ .description {
|
||||
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
|
||||
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
|
||||
~ .description::after {
|
||||
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
color: $legacy-portal-removal-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
overflow: auto;
|
||||
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
|
||||
background-color: #F7FAFC;
|
||||
padding: 10px;
|
||||
.wiz-choice{
|
||||
&:checked ~ .description {
|
||||
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
|
||||
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
|
||||
~ .description::after {
|
||||
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
color: $legacy-portal-removal-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(:checked) ~ label .setup-extension-tag.checked{
|
||||
display:none;
|
||||
}
|
||||
&:checked ~ label .setup-extension-tag.unchecked{
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
body {
|
||||
font-size: 1.17rem;
|
||||
font-family: "Raleway";
|
||||
@@ -595,6 +601,35 @@ body {
|
||||
color: $ibo-color-blue-700;
|
||||
font-size: $ibo-font-size-200;
|
||||
}
|
||||
.setup-extension--missing .setup-extension--icon{
|
||||
color:#a00000;
|
||||
}
|
||||
.setup-extension-tag {
|
||||
background-color: grey;
|
||||
border-radius: 8px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
margin-right: 3px;
|
||||
&.installed{
|
||||
background-color:#9eff9e
|
||||
}
|
||||
&.notinstalled{
|
||||
background-color:#ed9eff
|
||||
}
|
||||
&.tobeinstalled{
|
||||
background-color:#9ef0ff
|
||||
}
|
||||
&.tobeuninstalled{
|
||||
background-color:#ff9e9e
|
||||
}
|
||||
&.notuninstallable{
|
||||
background-color:#ffc98c
|
||||
}
|
||||
&.removed{
|
||||
background-color: #969594
|
||||
}
|
||||
}
|
||||
|
||||
.setup--wizard-choice--label + .setup--wizard-choice--more-info {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Service\Events\iEventServiceSetup;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
|
||||
class AttachmentPlugIn implements iApplicationUIExtension, iEventServiceSetup
|
||||
{
|
||||
@@ -236,10 +238,14 @@ class AttachmentPlugIn implements iApplicationUIExtension, iEventServiceSetup
|
||||
}
|
||||
$oAttachmentsRenderer = AttachmentsRendererFactory::GetInstance($oPage, $sObjClass, $iObjKey, $sTransactionId);
|
||||
|
||||
$iCount = $oAttachmentsRenderer->GetAttachmentsSet()->Count() + $oAttachmentsRenderer->GetTempAttachmentsSet()->Count();
|
||||
$sTitle = ($iCount > 0) ? Dict::Format('Attachments:TabTitle_Count', $iCount) : Dict::S('Attachments:EmptyTabTitle');
|
||||
if ($this->GetAttachmentsPosition() === 'relations') {
|
||||
$iCount = $oAttachmentsRenderer->GetAttachmentsSet()->Count() + $oAttachmentsRenderer->GetTempAttachmentsSet()->Count();
|
||||
$sTitle = ($iCount > 0) ? Dict::Format('Attachments:TabTitle_Count', $iCount) : Dict::S('Attachments:EmptyTabTitle');
|
||||
$oPage->SetCurrentTab('Attachments:Tab', $sTitle);
|
||||
} else {
|
||||
$oBlock = FieldSetUIBlockFactory::MakeStandard($sTitle);
|
||||
$oBlock->AddSubBlock(new Html(''));
|
||||
$oPage->AddUiBlock($oBlock);
|
||||
}
|
||||
|
||||
$bIsReadOnlyState = self::IsReadonlyState($oObject, $oObject->GetState(), AttachmentPlugIn::ENUM_GUI_BACKOFFICE);
|
||||
|
||||
@@ -67,15 +67,17 @@ class EventListener implements iEventServiceSetup
|
||||
/** @var \DBObject $oAttachment */
|
||||
$oAttachment = $oEventData->Get('object');
|
||||
$oHostObj = MetaModel::GetObject($oAttachment->Get('item_class'), $oAttachment->Get('item_id'), false /* false to avoid exception during trigger */, true);
|
||||
/** @var \ormDocument $oDocument */
|
||||
$oDocument = $oEventData->Get('document');
|
||||
if ($oHostObj != null) {
|
||||
/** @var \ormDocument $oDocument */
|
||||
$oDocument = $oEventData->Get('document');
|
||||
|
||||
$this->OnAttachmentActivateTriggers(
|
||||
$oHostObj,
|
||||
$oAttachment,
|
||||
$oDocument,
|
||||
TriggerOnAttachmentDownload::class
|
||||
);
|
||||
$this->OnAttachmentActivateTriggers(
|
||||
$oHostObj,
|
||||
$oAttachment,
|
||||
$oDocument,
|
||||
TriggerOnAttachmentDownload::class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -323,18 +323,38 @@
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="stock">
|
||||
<code>stock</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-neutral-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-neutral-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>40</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -6897,14 +6917,29 @@
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -135,7 +135,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment
|
||||
$aAvailableModules[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
// TODO check the auto-selected modules here
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if ($oExtension->bMarkedAsChosen) {
|
||||
foreach ($oExtension->aModules as $sModuleName) {
|
||||
if (!isset($aRet[$sModuleName]) && isset($aAvailableModules[$sModuleName])) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
* @param string $sEnvironment
|
||||
@@ -9,21 +10,18 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
public function __construct($sEnvironment = 'production', $bAutoCommit = true)
|
||||
{
|
||||
parent::__construct($sEnvironment, $bAutoCommit);
|
||||
|
||||
if ($sEnvironment != $this->sTargetEnv)
|
||||
{
|
||||
if (is_dir(APPROOT.'/env-'.$this->sTargetEnv))
|
||||
{
|
||||
SetupUtils::rrmdir(APPROOT.'/env-'.$this->sTargetEnv);
|
||||
}
|
||||
if (is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules'))
|
||||
{
|
||||
SetupUtils::rrmdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules');
|
||||
}
|
||||
|
||||
if ($sEnvironment != $this->sTargetEnv) {
|
||||
if (is_dir(APPROOT.'/env-'.$this->sTargetEnv)) {
|
||||
SetupUtils::rrmdir(APPROOT.'/env-'.$this->sTargetEnv);
|
||||
}
|
||||
if (is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
|
||||
SetupUtils::rrmdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules');
|
||||
}
|
||||
SetupUtils::copydir(APPROOT.'/data/'.$sEnvironment.'-modules', APPROOT.'/data/'.$this->sTargetEnv.'-modules');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the includes for the target environment
|
||||
* @param Config $oConfig
|
||||
@@ -32,7 +30,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
$oConfig->UpdateIncludes('env-'.$this->sTargetEnv); // TargetEnv != FinalEnv
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move an extension (path to folder of this extension) to the target environment
|
||||
* @param string $sExtensionDirectory The folder of the extension
|
||||
@@ -40,21 +38,23 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
*/
|
||||
public function MoveExtension($sExtensionDirectory)
|
||||
{
|
||||
if (!is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules'))
|
||||
{
|
||||
if (!mkdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) throw new Exception("ERROR: failed to create directory:'".(APPROOT.'/data/'.$this->sTargetEnv.'-modules')."'");
|
||||
if (!is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
|
||||
if (!mkdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
|
||||
throw new Exception("ERROR: failed to create directory:'".(APPROOT.'/data/'.$this->sTargetEnv.'-modules')."'");
|
||||
}
|
||||
}
|
||||
$sDestinationPath = APPROOT.'/data/'.$this->sTargetEnv.'-modules/';
|
||||
|
||||
|
||||
// Make sure that the destination directory of the extension does not already exist
|
||||
if (is_dir($sDestinationPath.basename($sExtensionDirectory)))
|
||||
{
|
||||
// Cleanup before moving...
|
||||
SetupUtils::rrmdir($sDestinationPath.basename($sExtensionDirectory));
|
||||
if (is_dir($sDestinationPath.basename($sExtensionDirectory))) {
|
||||
// Cleanup before moving...
|
||||
SetupUtils::rrmdir($sDestinationPath.basename($sExtensionDirectory));
|
||||
}
|
||||
if (!rename($sExtensionDirectory, $sDestinationPath.basename($sExtensionDirectory))) {
|
||||
throw new Exception("ERROR: failed move directory:'$sExtensionDirectory' to '".$sDestinationPath.basename($sExtensionDirectory)."'");
|
||||
}
|
||||
if (!rename($sExtensionDirectory, $sDestinationPath.basename($sExtensionDirectory))) throw new Exception("ERROR: failed move directory:'$sExtensionDirectory' to '".$sDestinationPath.basename($sExtensionDirectory)."'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move the selected extensions located in the given directory in data/<target-env>-modules
|
||||
* @param string $sDownloadedExtensionsDir The directory to scan
|
||||
@@ -63,10 +63,8 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
*/
|
||||
public function MoveSelectedExtensions($sDownloadedExtensionsDir, $aSelectedExtensionDirs)
|
||||
{
|
||||
foreach(glob($sDownloadedExtensionsDir.'*', GLOB_ONLYDIR) as $sExtensionDir)
|
||||
{
|
||||
if (in_array(basename($sExtensionDir), $aSelectedExtensionDirs))
|
||||
{
|
||||
foreach (glob($sDownloadedExtensionsDir.'*', GLOB_ONLYDIR) as $sExtensionDir) {
|
||||
if (in_array(basename($sExtensionDir), $aSelectedExtensionDirs)) {
|
||||
$this->MoveExtension($sExtensionDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ function DisplayStatus(WebPage $oPage)
|
||||
if (is_dir($sPath)) {
|
||||
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
|
||||
}
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', true, $aExtraDirs);
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', $aExtraDirs);
|
||||
$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('production', true, $aExtraDirs);
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', $aExtraDirs);
|
||||
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
|
||||
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
|
||||
@@ -186,9 +186,7 @@ function collect_configuration()
|
||||
|
||||
// iTop modules
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
// Get the latest installed modules, without the "root" ones (iTop version and datamodel version)
|
||||
$aInstalledModules = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install WHERE installed = '".$sLatestInstallationDate."' AND parent_id != 0");
|
||||
$aInstalledModules = ModuleInstallationService::GetInstance()->ReadFromDB($oConfig);
|
||||
|
||||
foreach ($aInstalledModules as $aDBInfo) {
|
||||
$aConfiguration['itop_modules'][$aDBInfo['name']] = $aDBInfo['version'];
|
||||
|
||||
@@ -17,6 +17,7 @@ SetupWebPage::AddModule(
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-welcome-itil/3.1.0,',
|
||||
'itop-profiles-itil/3.1.0', //SuperUser id 117
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
|
||||
@@ -28,6 +28,7 @@ SetupWebPage::AddModule(
|
||||
'category' => 'Portal',
|
||||
// Setup
|
||||
'dependencies' => [
|
||||
'itop-attachments/3.2.1', //CMDBChangeOpAttachmentRemoved
|
||||
],
|
||||
'mandatory' => true,
|
||||
'visible' => false,
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -176,17 +179,32 @@
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1133,6 +1151,9 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="organization_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1184,17 +1205,32 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1537,6 +1573,9 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="service_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1585,17 +1624,32 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -176,17 +179,32 @@
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1122,6 +1140,9 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="organization_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1173,17 +1194,32 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1548,6 +1584,9 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="service_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1596,17 +1635,32 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -1486,14 +1486,29 @@
|
||||
<value id="draft">
|
||||
<code>draft</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="published">
|
||||
<code>published</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -13,6 +13,7 @@ SetupWebPage::AddModule(
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/2.7.1',
|
||||
'itop-portal/3.0.0', // module_design_itop_design->module_designs->itop-portal
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
|
||||
@@ -43,18 +43,38 @@
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="stock">
|
||||
<code>stock</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-neutral-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-neutral-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>40</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -14,7 +14,10 @@ if (PHP_VERSION_ID < 50600) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
@@ -26,23 +26,12 @@ use Composer\Semver\VersionParser;
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
@@ -320,24 +309,6 @@ class InstalledVersions
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,9 +325,7 @@ class InstalledVersions
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
@@ -364,14 +333,11 @@ class InstalledVersions
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ return array(
|
||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
||||
'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'),
|
||||
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'),
|
||||
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'),
|
||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||
|
||||
@@ -340,8 +340,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
),
|
||||
'League\\OAuth2\\Client\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
||||
1 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
||||
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
||||
1 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
||||
),
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
array (
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'name' => 'combodo/itop',
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
|
||||
'reference' => '469afdb2f9aea1b6e078a2a5bb12f09a969d60e0',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -22,7 +22,7 @@
|
||||
'combodo/itop' => array(
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
|
||||
'reference' => '469afdb2f9aea1b6e078a2a5bb12f09a969d60e0',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
||||
@@ -36,7 +36,8 @@ if ($issues) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
130
setup/AnalyzeInstallation.php
Normal file
130
setup/AnalyzeInstallation.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/ModuleInstallationService.php';
|
||||
|
||||
class AnalyzeInstallation
|
||||
{
|
||||
private static AnalyzeInstallation $oInstance;
|
||||
private ?array $aAvailableModules = null;
|
||||
private ?array $aSelectInstall = null;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): AnalyzeInstallation
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new AnalyzeInstallation();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?AnalyzeInstallation $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the current installation and the possibilities
|
||||
*
|
||||
* @param null|Config $oConfig Defines the target environment (DB)
|
||||
* @param mixed $modulesPath Either a single string or an array of absolute paths
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array Array with the following format:
|
||||
* array =>
|
||||
* 'iTop' => array(
|
||||
* 'installed_version' => ... (could be empty in case of a fresh install)
|
||||
* 'available_version => ...
|
||||
* )
|
||||
* <module_name> => array(
|
||||
* 'installed_version' => ...
|
||||
* 'available_version' => ...
|
||||
* 'install' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
* )
|
||||
* 'uninstall' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
* )
|
||||
* 'label' => ...
|
||||
* 'dependencies' => array(<module1>, <module2>, ...)
|
||||
* 'visible' => true | false
|
||||
* )
|
||||
* )
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function AnalyzeInstallation(?Config $oConfig, mixed $modulesPath, bool $bAbortOnMissingDependency = false, ?array $aModulesToLoad = null)
|
||||
{
|
||||
$aRes = [
|
||||
ROOT_MODULE => [
|
||||
'installed_version' => '',
|
||||
'available_version' => ITOP_VERSION_FULL,
|
||||
'name_code' => ITOP_APPLICATION,
|
||||
],
|
||||
];
|
||||
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
|
||||
if (! is_null($this->aAvailableModules)) {
|
||||
//test only
|
||||
$aAvailableModules = $this->aAvailableModules;
|
||||
} else {
|
||||
$aAvailableModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
foreach ($aAvailableModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
$aModuleInfo['installed_version'] = '';
|
||||
$aModuleInfo['available_version'] = $sModuleVersion;
|
||||
|
||||
if ($aModuleInfo['mandatory']) {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_MANDATORY,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
$aRes[$sModuleName] = $aModuleInfo;
|
||||
}
|
||||
|
||||
$aCurrentlyInstalledModules = ModuleInstallationService::GetInstance()->ReadComputeInstalledModules($oConfig);
|
||||
|
||||
// Adjust the list of proposed modules
|
||||
foreach ($aCurrentlyInstalledModules as $sModuleName => $aModuleDB) {
|
||||
if ($sModuleName == ROOT_MODULE) {
|
||||
$aRes[$sModuleName]['installed_version'] = $aModuleDB['version'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($sModuleName, $aRes)) {
|
||||
// A module was installed, it is not proposed in the new build... skip
|
||||
continue;
|
||||
}
|
||||
|
||||
$aRes[$sModuleName]['installed_version'] = $aModuleDB['version'];
|
||||
|
||||
if ($aRes[$sModuleName]['mandatory']) {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
}
|
||||
184
setup/ModuleInstallationService.php
Normal file
184
setup/ModuleInstallationService.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
class ModuleInstallationService
|
||||
{
|
||||
private static ModuleInstallationService $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModuleInstallationService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModuleInstallationService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModuleInstallationService $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
private ?array $aSelectInstall = null;
|
||||
|
||||
/**
|
||||
* @param \Config|null $oConfig
|
||||
* @return array
|
||||
*/
|
||||
public function ReadComputeInstalledModules(?Config $oConfig): array
|
||||
{
|
||||
$aSelectInstall = [];
|
||||
try {
|
||||
$aSelectInstall = $this->ReadFromDB($oConfig);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
}
|
||||
|
||||
return $this->ComputeInstalledModules($aSelectInstall);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config|null $oConfig
|
||||
* @return array
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
*/
|
||||
public function ReadFromDB(?Config $oConfig): array
|
||||
{
|
||||
if (is_null($oConfig)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! is_null($this->aSelectInstall)) {
|
||||
//test only
|
||||
return $this->aSelectInstall;
|
||||
}
|
||||
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
//read db module installations
|
||||
$tableWithPrefix = $this->GetTableWithPrefix($oConfig);
|
||||
$iRootId = CMDBSource::QueryToScalar("SELECT max(parent_id) FROM $tableWithPrefix");
|
||||
// Get the latest installed modules, without the "root" ones (iTop version and datamodel version)
|
||||
$sSQL = <<<SQL
|
||||
SELECT * FROM $tableWithPrefix
|
||||
WHERE
|
||||
parent_id='$iRootId'
|
||||
OR id='$iRootId'
|
||||
SQL;
|
||||
return CMDBSource::QueryToArray($sSQL);
|
||||
}
|
||||
|
||||
private function GetTableWithPrefix(Config $oConfig)
|
||||
{
|
||||
$sPrefix = $oConfig->Get('db_subname');
|
||||
if (utils::IsNullOrEmptyString($sPrefix)) {
|
||||
return "priv_module_install";
|
||||
}
|
||||
|
||||
return "{$sPrefix}priv_module_install";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config $oConfig
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function GetApplicationVersion(Config $oConfig)
|
||||
{
|
||||
try {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$tableWithPrefix = $this->GetTableWithPrefix($oConfig);
|
||||
$sSQLQuery = "SELECT * FROM $tableWithPrefix";
|
||||
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
$this->log_error('Can not connect to the database: host: '.$oConfig->Get('db_host').', user:'.$oConfig->Get('db_user').', pwd:'.$oConfig->Get('db_pwd').', db name:'.$oConfig->Get('db_name'));
|
||||
$this->log_error('Exception '.$e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$aResult = [];
|
||||
// Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
if ($aInstall['name'] == DATAMODEL_MODULE) {
|
||||
$aResult['datamodel_version'] = $sModuleVersion;
|
||||
$aComments = json_decode($aInstall['comment'], true);
|
||||
if (is_array($aComments)) {
|
||||
$aResult = array_merge($aResult, $aComments);
|
||||
}
|
||||
} else {
|
||||
$aResult['product_name'] = $aInstall['name'];
|
||||
$aResult['product_version'] = $sModuleVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('datamodel_version', $aResult)) {
|
||||
// Versions prior to 2.0 did not record the version of the datamodel
|
||||
// so assume that the datamodel version is equal to the application version
|
||||
$aResult['datamodel_version'] = $aResult['product_version'];
|
||||
}
|
||||
$this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']);
|
||||
return empty($aResult) ? false : $aResult;
|
||||
}
|
||||
|
||||
private function ComputeInstalledModules(array $aSelectInstall): array
|
||||
{
|
||||
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
|
||||
//module installation datetime is mostly the same for all modules
|
||||
//unless there was issue recording things in DB
|
||||
$sFirstDatetime = null;
|
||||
$iFirstTime = -1;
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
//$aInstall['comment']; // unsused
|
||||
$sDatetime = $aInstall['installed'];
|
||||
|
||||
if (is_null($sFirstDatetime)) {
|
||||
$sFirstDatetime = $sDatetime;
|
||||
$iFirstTime = strtotime($sDatetime);
|
||||
$iInstalled = $iFirstTime;
|
||||
} elseif ($sDatetime === $sFirstDatetime) {
|
||||
$iInstalled = $iFirstTime;
|
||||
} else {
|
||||
$sDatetime = $aInstall['installed'];
|
||||
$iInstalled = strtotime($sDatetime);
|
||||
}
|
||||
|
||||
$sModuleName = $aInstall['name'];
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$aInstallByModule[ROOT_MODULE] = [
|
||||
'installed_version' => $sModuleVersion,
|
||||
'installed' => $iInstalled,
|
||||
'version' => $sModuleVersion,
|
||||
];
|
||||
} else {
|
||||
$aInstallByModule[$sModuleName] = [
|
||||
'installed' => $iInstalled,
|
||||
'version' => $sModuleVersion,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aInstallByModule;
|
||||
}
|
||||
}
|
||||
@@ -477,7 +477,7 @@ class MFCompiler
|
||||
$sClass = $oClass->getAttribute("id");
|
||||
$aAllClasses[] = $sClass;
|
||||
try {
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
} catch (DOMFormatException $e) {
|
||||
$sMessage = "Failed to process class '$sClass', ";
|
||||
if (!empty($sModuleRootDir)) {
|
||||
@@ -1189,6 +1189,7 @@ EOF
|
||||
|
||||
/**
|
||||
* @param \MFElement $oClass
|
||||
* @param string $sModuleName
|
||||
* @param string $sTempTargetDir
|
||||
* @param string $sFinalTargetDir
|
||||
* @param string $sModuleRelativeDir
|
||||
@@ -1196,7 +1197,7 @@ EOF
|
||||
* @return string
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
protected function CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
protected function CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
{
|
||||
$sClass = $oClass->getAttribute('id');
|
||||
$oProperties = $oClass->GetUniqueElement('properties');
|
||||
@@ -1209,6 +1210,7 @@ EOF
|
||||
$aClassParams = [];
|
||||
$aClassParams['category'] = $this->GetPropString($oProperties, 'category', '');
|
||||
$aClassParams['key_type'] = "'autoincrement'";
|
||||
$aClassParams['created_in'] = "'$sModuleName'";
|
||||
if ((bool)$this->GetPropNumber($oProperties, 'is_link', 0)) {
|
||||
$aClassParams['is_link'] = 'true';
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
97
setup/feature_removal/DryRemovalRuntimeEnvironment.php
Normal file
97
setup/feature_removal/DryRemovalRuntimeEnvironment.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use MetaModel;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
|
||||
class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
public const DRY_REMOVAL_AUDIT_ENV = "extension-removal";
|
||||
|
||||
protected array $aExtensionsByCode;
|
||||
private bool $bExtensionMapModified = false;
|
||||
|
||||
/**
|
||||
* Toolset for building a run-time environment
|
||||
*
|
||||
* @param string $sEnvironment (e.g. 'test')
|
||||
* @param bool $bAutoCommit (make the target environment directly, or build a temporary one)
|
||||
*/
|
||||
public function __construct($sEnvironment = self::DRY_REMOVAL_AUDIT_ENV, $bAutoCommit = true)
|
||||
{
|
||||
parent::__construct($sEnvironment, $bAutoCommit);
|
||||
$this->aExtensionsByCode = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSourceEnv
|
||||
* @param array $aExtensionCodesToRemove
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function Prepare(string $sSourceEnv, array $aExtensionCodesToRemove)
|
||||
{
|
||||
|
||||
$sEnv = $this->sFinalEnv;
|
||||
$this->aExtensionsByCode = $aExtensionCodesToRemove;
|
||||
//SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
|
||||
$this->Cleanup();
|
||||
SetupUtils::copydir(APPROOT."/data/$sSourceEnv-modules", APPROOT."/data/$sEnv-modules");
|
||||
|
||||
if (count($aExtensionCodesToRemove) > 0) {
|
||||
$this->RemoveExtensionsLocally($aExtensionCodesToRemove);
|
||||
}
|
||||
$oDryRemovalConfig = clone(MetaModel::GetConfig());
|
||||
$oDryRemovalConfig->ChangeModulesPath($sSourceEnv, $this->sFinalEnv);
|
||||
$this->WriteConfigFileSafe($oDryRemovalConfig);
|
||||
}
|
||||
|
||||
private function RemoveExtensionsLocally(array $aExtensionCodes): void
|
||||
{
|
||||
$oExtensionsMap = new \iTopExtensionsMap($this->sFinalEnv);
|
||||
|
||||
foreach ($aExtensionCodes as $sCode) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
$oExtension = $oExtensionsMap->GetFromExtensionCode($sCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$sDir = $oExtension->sSourceDir;
|
||||
\IssueLog::Info(__METHOD__.": remove extension locally", null, [$oExtension->sCode => $sDir]);
|
||||
SetupUtils::rrmdir($sDir);
|
||||
} else {
|
||||
\IssueLog::Warning(__METHOD__." cannot find extensions", null, ['env' => $this->sFinalEnv, 'code' => $sCode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function Cleanup()
|
||||
{
|
||||
$sEnv = $this->sFinalEnv;
|
||||
SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
|
||||
SetupUtils::rrmdir(APPROOT."/data/cache-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/env-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/conf/$sEnv");
|
||||
@unlink(APPROOT."/data/datamodel-$sEnv.xml");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \iTopExtensionsMap|null
|
||||
*/
|
||||
/*protected function GetExtensionMap(): ?iTopExtensionsMap
|
||||
{
|
||||
if (is_null(parent::GetExtensionMap())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->bExtensionMapModified) {
|
||||
$this->bExtensionMapModified = true;
|
||||
foreach ($this->aExtensionsByCode as $sCode) {
|
||||
parent::GetExtensionMap()->RemoveExtension($sCode);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::GetExtensionMap();
|
||||
}*/
|
||||
}
|
||||
55
setup/feature_removal/ModelReflectionSerializer.php
Normal file
55
setup/feature_removal/ModelReflectionSerializer.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use CoreException;
|
||||
use Exception;
|
||||
|
||||
class ModelReflectionSerializer
|
||||
{
|
||||
private static ModelReflectionSerializer $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModelReflectionSerializer
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModelReflectionSerializer();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModelReflectionSerializer $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function GetModelFromEnvironment(string $sEnv): array
|
||||
{
|
||||
\IssueLog::Info(__METHOD__, null, ['env' => $sEnv]);
|
||||
$sPHPExec = trim(\MetaModel::GetConfig()->Get('php_path'));
|
||||
$sOutput = "";
|
||||
$iRes = 0;
|
||||
exec(sprintf("$sPHPExec %s/get_model_reflection.php --env='%s'", __DIR__, $sEnv), $sOutput, $iRes);
|
||||
if ($iRes != 0) {
|
||||
\IssueLog::Error("Cannot get classes", null, ['env' => $sEnv, 'code' => $iRes, "output" => $sOutput]);
|
||||
throw new CoreException("Cannot get classes");
|
||||
}
|
||||
|
||||
$aClasses = json_decode($sOutput[0] ?? null, true);
|
||||
if (false === $aClasses) {
|
||||
\IssueLog::Error("Invalid JSON", null, ["output" => $sOutput]);
|
||||
throw new Exception("cannot get classes");
|
||||
}
|
||||
|
||||
if (!is_array($aClasses)) {
|
||||
\IssueLog::Error("not an array", null, ["classes" => $aClasses]);
|
||||
throw new Exception("cannot get classes");
|
||||
}
|
||||
|
||||
return $aClasses;
|
||||
}
|
||||
}
|
||||
126
setup/feature_removal/SetupAudit.php
Normal file
126
setup/feature_removal/SetupAudit.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use MetaModel;
|
||||
|
||||
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
|
||||
|
||||
class SetupAudit
|
||||
{
|
||||
//file used when present to trigger audit exception when testing specific setups
|
||||
public const GETISSUE_ERROR_MSG_FILE_FORTESTONLY = '.setup_audit_error_msg.txt';
|
||||
|
||||
private string $sEnvBeforeExtensionRemoval;
|
||||
private string $sEnvAfterExtensionRemoval;
|
||||
|
||||
private array $aClassesBeforeRemoval;
|
||||
private array $aClassesAfterRemoval;
|
||||
private array $aRemovedClasses;
|
||||
private array $aFinalClassesRemoved;
|
||||
|
||||
public function __construct(string $sEnvBeforeExtensionRemoval, string $sEnvAfterExtensionRemoval = DryRemovalRuntimeEnvironment::DRY_REMOVAL_AUDIT_ENV)
|
||||
{
|
||||
$this->sEnvBeforeExtensionRemoval = $sEnvBeforeExtensionRemoval;
|
||||
$this->sEnvAfterExtensionRemoval = $sEnvAfterExtensionRemoval;
|
||||
|
||||
$sCurrentEnvt = MetaModel::GetEnvironment();
|
||||
if ($sCurrentEnvt === $this->sEnvBeforeExtensionRemoval) {
|
||||
$this->aClassesBeforeRemoval = MetaModel::GetClasses();
|
||||
} else {
|
||||
$this->aClassesBeforeRemoval = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvBeforeExtensionRemoval);
|
||||
}
|
||||
|
||||
if ($sCurrentEnvt === $this->sEnvAfterExtensionRemoval) {
|
||||
$this->aClassesAfterRemoval = MetaModel::GetClasses();
|
||||
} else {
|
||||
$this->aClassesAfterRemoval = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvAfterExtensionRemoval);
|
||||
}
|
||||
|
||||
$this->aRemovedClasses = [];
|
||||
$this->aFinalClassesRemoved = [];
|
||||
}
|
||||
|
||||
/*public function SetSelectedExtensions(Config $oConfig, array $aSelectedExtensions)
|
||||
{
|
||||
$oExtensionsMap = new \iTopExtensionsMap();
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
|
||||
sort($aSelectedExtensions);
|
||||
$this->aExtensionToRemove = $oExtensionsMap->GetMissingExtensions($aSelectedExtensions);
|
||||
sort($this->aExtensionToRemove);
|
||||
\SetupLog::Info(__METHOD__, null, ['aExtensionToRemove' => $this->aExtensionToRemove]);
|
||||
}*/
|
||||
|
||||
public function GetRemovedClasses(): array
|
||||
{
|
||||
if (count($this->aRemovedClasses) == 0) {
|
||||
if (count($this->aClassesBeforeRemoval) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
if (count($this->aClassesAfterRemoval) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
$aExtensionsNames = array_diff($this->aClassesBeforeRemoval, $this->aClassesAfterRemoval);
|
||||
$this->aRemovedClasses = [];
|
||||
$aClasses = array_values($aExtensionsNames);
|
||||
sort($aClasses);
|
||||
|
||||
foreach ($aClasses as $i => $sClass) {
|
||||
$this->aRemovedClasses[] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
/** test only: return file path that force audit error being raised
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function GetErrorMessageFilePathForTestOnly(): string
|
||||
{
|
||||
return APPROOT."/data/".self::GETISSUE_ERROR_MSG_FILE_FORTESTONLY;
|
||||
}
|
||||
|
||||
public function GetIssues(bool $bThrowExceptionAtFirstIssue = false): array
|
||||
{
|
||||
$sErrorMessageFilePath = self::GetErrorMessageFilePathForTestOnly();
|
||||
if ($bThrowExceptionAtFirstIssue && is_file($sErrorMessageFilePath)) {
|
||||
$sMsg = file_get_contents($sErrorMessageFilePath);
|
||||
throw new \Exception($sMsg);
|
||||
}
|
||||
|
||||
$this->aFinalClassesRemoved = [];
|
||||
|
||||
foreach ($this->GetRemovedClasses() as $sClass) {
|
||||
if (MetaModel::IsAbstract($sClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MetaModel::IsStandaloneClass($sClass)) {
|
||||
$iCount = $this->Count($sClass);
|
||||
$this->aFinalClassesRemoved[$sClass] = $iCount;
|
||||
if ($bThrowExceptionAtFirstIssue && $iCount > 0) {
|
||||
//setup envt: should raise issue ASAP
|
||||
throw new \Exception($sClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aFinalClassesRemoved;
|
||||
}
|
||||
|
||||
private function Count($sClass): int
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sClass", []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
return $oSet->Count();
|
||||
}
|
||||
}
|
||||
41
setup/feature_removal/get_model_reflection.php
Normal file
41
setup/feature_removal/get_model_reflection.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
require_once(dirname(__DIR__, 2).'/approot.inc.php');
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
$sEnv = null;
|
||||
if (isset($argv)) {
|
||||
foreach ($argv as $iArg => $sArg) {
|
||||
if (preg_match('/^--env=(.*)$/', $sArg, $aMatches)) {
|
||||
$sEnv = $aMatches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($sEnv)) {
|
||||
echo "No environment provided (--env) to read datamodel.";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$sConfFile = utils::GetConfigFilePath($sEnv);
|
||||
|
||||
try {
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e->getMessage();
|
||||
echo $e->getTraceAsString();
|
||||
\SetupLog::Error(
|
||||
"Cannot read model from provided environment",
|
||||
null,
|
||||
[
|
||||
'env' => $sEnv,
|
||||
'error' => $e->getMessage(),
|
||||
'stack' => $e->getTraceAsString(),
|
||||
]
|
||||
);
|
||||
echo "Cannot read model from provided environment";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$aClasses = MetaModel::GetClasses();
|
||||
|
||||
echo json_encode($aClasses);
|
||||
155
setup/moduledependency/dependencyexpression.class.inc.php
Normal file
155
setup/moduledependency/dependencyexpression.class.inc.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
require_once(APPROOT.'/setup/runtimeenv.class.inc.php');
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use ModuleFileReaderException;
|
||||
use RunTimeEnvironment;
|
||||
|
||||
/**
|
||||
* Class that handles a module dependency
|
||||
* Dependency expression example : (moduleA/123 || moduleB>456)
|
||||
*/
|
||||
class DependencyExpression
|
||||
{
|
||||
private static PhpExpressionEvaluator $oPhpExpressionEvaluator;
|
||||
|
||||
private string $sDependencyExpression;
|
||||
private bool $bValid = true;
|
||||
private bool $bResolved = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool> $aRemainingModuleNamesToResolve
|
||||
*/
|
||||
private array $aRemainingModuleNamesToResolve;
|
||||
|
||||
/**
|
||||
* @var array<string, array> $aParamsPerModuleId
|
||||
*/
|
||||
private array $aParamsPerModuleId;
|
||||
|
||||
public function __construct(string $sDependencyExpression)
|
||||
{
|
||||
$this->sDependencyExpression = $sDependencyExpression;
|
||||
$this->aParamsPerModuleId = [];
|
||||
$this->aRemainingModuleNamesToResolve = [];
|
||||
|
||||
if (preg_match_all('/([^\(\)&| ]+)/', $sDependencyExpression, $aMatches)) {
|
||||
foreach ($aMatches as $aMatch) {
|
||||
foreach ($aMatch as $sModuleId) {
|
||||
if (!array_key_exists($sModuleId, $this->aParamsPerModuleId)) {
|
||||
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
|
||||
// where the operator is < <= = > >= (by default >=)
|
||||
$aModuleMatches = [];
|
||||
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
|
||||
$sModuleName = $aModuleMatches[1];
|
||||
$this->aRemainingModuleNamesToResolve[$sModuleName] = true;
|
||||
$sOperator = $aModuleMatches[2];
|
||||
if ($sOperator == '') {
|
||||
$sOperator = '>=';
|
||||
}
|
||||
$sExpectedVersion = $aModuleMatches[3];
|
||||
$this->aParamsPerModuleId[$sModuleId] = [$sModuleName, $sOperator, $sExpectedVersion];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->bValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
{
|
||||
if (!isset(static::$oPhpExpressionEvaluator)) {
|
||||
static::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
}
|
||||
|
||||
return static::$oPhpExpressionEvaluator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return module names potentially required by current dependency
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetRemainingModuleNamesToResolve(): array
|
||||
{
|
||||
return array_keys($this->aRemainingModuleNamesToResolve);
|
||||
}
|
||||
|
||||
public function IsResolved(): bool
|
||||
{
|
||||
return $this->bResolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if dependency is resolved with current list of module versions
|
||||
*
|
||||
* @param array $aResolvedModuleVersions : versions by module names dict
|
||||
* @param array $aAllModuleNames : modules names dict
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function UpdateModuleResolutionState(array $aResolvedModuleVersions, array $aAllModuleNames): void
|
||||
{
|
||||
if (!$this->bValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aReplacements = [];
|
||||
|
||||
$bDelayEvaluation = false;
|
||||
foreach ($this->aParamsPerModuleId as $sModuleId => list($sModuleName, $sOperator, $sExpectedVersion)) {
|
||||
if (array_key_exists($sModuleName, $aResolvedModuleVersions)) {
|
||||
// module is resolved, check the version
|
||||
$sCurrentVersion = $aResolvedModuleVersions[$sModuleName];
|
||||
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
|
||||
if (array_key_exists($sModuleName, $this->aRemainingModuleNamesToResolve)) {
|
||||
unset($this->aRemainingModuleNamesToResolve[$sModuleName]);
|
||||
}
|
||||
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
} else {
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
} else {
|
||||
// module is not resolved yet
|
||||
|
||||
if (array_key_exists($sModuleName, $aAllModuleNames)) {
|
||||
//Weird piece of code that covers below usecase:
|
||||
//module B dependency: 'moduleA || true'
|
||||
// if moduleA not present on disk, whole expression can be evaluated and may be resolved
|
||||
// if moduleA present on disk, we need to sort moduleB after moduleA. expression cannot be resolved yet
|
||||
$bDelayEvaluation = true;
|
||||
} else {
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($bDelayEvaluation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bResult = false;
|
||||
$sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $this->sDependencyExpression);
|
||||
try {
|
||||
$bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr);
|
||||
} catch (ModuleFileReaderException $e) {
|
||||
//logged already
|
||||
echo "Failed to parse the boolean Expression = '$sBooleanExpr'<br/>";
|
||||
}
|
||||
|
||||
$this->bResolved = $bResult;
|
||||
}
|
||||
|
||||
public function IsValid(): bool
|
||||
{
|
||||
return $this->bValid;
|
||||
}
|
||||
}
|
||||
129
setup/moduledependency/module.class.inc.php
Normal file
129
setup/moduledependency/module.class.inc.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
require_once(__DIR__.'/dependencyexpression.class.inc.php');
|
||||
use ModuleDiscovery;
|
||||
|
||||
/**
|
||||
* Class that handles a modules and all its dependencies
|
||||
*/
|
||||
class Module
|
||||
{
|
||||
private string $sModuleId;
|
||||
private string $sModuleName;
|
||||
private string $sVersion;
|
||||
|
||||
/**
|
||||
* @var array<string> $aInitialDependencyExpressions
|
||||
*/
|
||||
private array $aInitialDependencyExpressions;
|
||||
|
||||
/**
|
||||
* @var array<string, DependencyExpression> $aRemainingDependenciesToResolve
|
||||
*/
|
||||
public array $aRemainingDependenciesToResolve;
|
||||
|
||||
public function __construct(string $sModuleId)
|
||||
{
|
||||
$this->sModuleId = $sModuleId;
|
||||
list($this->sModuleName, $this->sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
}
|
||||
|
||||
public function IsDependencyExpressionResolved(string $sDependencyExpression): bool
|
||||
{
|
||||
return ! array_key_exists($sDependencyExpression, $this->aRemainingDependenciesToResolve);
|
||||
}
|
||||
|
||||
public function GetDependencyResolutionFeedback(): array
|
||||
{
|
||||
$aDepsWithIcons = [];
|
||||
|
||||
foreach ($this->aInitialDependencyExpressions as $sDependencyExpression) {
|
||||
if (! $this->IsDependencyExpressionResolved($sDependencyExpression)) {
|
||||
$aDepsWithIcons[] = '❌ '.$sDependencyExpression;
|
||||
}
|
||||
}
|
||||
return $aDepsWithIcons;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetModuleName()
|
||||
{
|
||||
return $this->sModuleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetVersion()
|
||||
{
|
||||
return $this->sVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetModuleId()
|
||||
{
|
||||
return $this->sModuleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aAllDependencyExpressions: list of dependencies (string)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetDependencies(array $aAllDependencyExpressions): void
|
||||
{
|
||||
$this->aInitialDependencyExpressions = $aAllDependencyExpressions;
|
||||
$this->aRemainingDependenciesToResolve = [];
|
||||
|
||||
foreach ($aAllDependencyExpressions as $sDependencyExpression) {
|
||||
$this->aRemainingDependenciesToResolve[$sDependencyExpression] = new DependencyExpression($sDependencyExpression);
|
||||
}
|
||||
}
|
||||
|
||||
public function IsResolved(): bool
|
||||
{
|
||||
return (0 === count($this->aRemainingDependenciesToResolve));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if module dependencies are resolved with current list of module versions
|
||||
* @param array<string, string> $aResolvedModuleVersions : versions by module names dict
|
||||
* @param array<string> $aAllModuleNames : resolved modules names
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function UpdateModuleResolutionState(array $aResolvedModuleVersions, array $aAllModuleNames): void
|
||||
{
|
||||
$aNextDependencies = [];
|
||||
|
||||
foreach ($this->aRemainingDependenciesToResolve as $sDependencyExpression => $oModuleDependency) {
|
||||
/** @var DependencyExpression $oModuleDependency*/
|
||||
$oModuleDependency->UpdateModuleResolutionState($aResolvedModuleVersions, $aAllModuleNames);
|
||||
if (!$oModuleDependency->IsResolved()) {
|
||||
$aNextDependencies[$sDependencyExpression] = $oModuleDependency;
|
||||
}
|
||||
}
|
||||
|
||||
$this->aRemainingDependenciesToResolve = $aNextDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array: list of unique module names
|
||||
*/
|
||||
public function GetUnresolvedDependencyModuleNames(): array
|
||||
{
|
||||
$aRes = [];
|
||||
foreach ($this->aRemainingDependenciesToResolve as $sDependencyExpression => $oModuleDependency) {
|
||||
/** @var DependencyExpression $oModuleDependency */
|
||||
$aRes = array_merge($aRes, $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
|
||||
return array_unique($aRes);
|
||||
}
|
||||
}
|
||||
201
setup/moduledependency/moduledependencysort.class.inc.php
Normal file
201
setup/moduledependency/moduledependencysort.class.inc.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
require_once(__DIR__.'/module.class.inc.php');
|
||||
|
||||
use MissingDependencyException;
|
||||
|
||||
/**
|
||||
* Class that sorts module dependencies
|
||||
*/
|
||||
class ModuleDependencySort
|
||||
{
|
||||
private static ModuleDependencySort $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModuleDependencySort
|
||||
{
|
||||
if (!isset(static::$oInstance)) {
|
||||
static::$oInstance = new static();
|
||||
}
|
||||
|
||||
return static::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModuleDependencySort $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of modules, based on their (inter) dependencies
|
||||
*
|
||||
* @param array $aModules The list of modules to process: 'id' => $aModuleInfo
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
*
|
||||
* @return array
|
||||
* @throws \MissingDependencyException
|
||||
*/
|
||||
public function GetModulesOrderedForInstallation($aModules, $bAbortOnMissingDependency = false)
|
||||
{
|
||||
// Filter modules to compute
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$aAllModuleNames = [];
|
||||
foreach ($aModules as $sModuleId => $aModule) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
$oModule->SetDependencies($aModule['dependencies']);
|
||||
$aUnresolvedDependencyModules[$sModuleId] = $oModule;
|
||||
$aAllModuleNames[$sModuleName] = true;
|
||||
}
|
||||
|
||||
// Make sure order is deterministic (alphabtical order)
|
||||
ksort($aUnresolvedDependencyModules);
|
||||
|
||||
//Attempt to resolve module dependencies
|
||||
$aOrderedModules = [];
|
||||
$aResolvedModuleVersions = [];
|
||||
$iPreviousUnresolvedCount = -1;
|
||||
//loop until no dependency is resolved
|
||||
while ($iPreviousUnresolvedCount !== count($aUnresolvedDependencyModules)) {
|
||||
$iPreviousUnresolvedCount = count($aUnresolvedDependencyModules);
|
||||
if ($iPreviousUnresolvedCount === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
/** @var Module $oModule */
|
||||
$oModule->UpdateModuleResolutionState($aResolvedModuleVersions, $aAllModuleNames);
|
||||
if ($oModule->IsResolved()) {
|
||||
$aOrderedModules[] = $sModuleId;
|
||||
$aResolvedModuleVersions[$oModule->GetModuleName()] = $oModule->GetVersion();
|
||||
unset($aUnresolvedDependencyModules[$sModuleId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report unresolved dependencies
|
||||
if ($bAbortOnMissingDependency && count($aUnresolvedDependencyModules) > 0) {
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
|
||||
$aUnresolvedModulesInfo = [];
|
||||
$aModuleDeps = [];
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
$aModule = $aModules[$sModuleId];
|
||||
$aDepsWithIcons = $oModule->GetDependencyResolutionFeedback();
|
||||
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sModuleId) depends on: ".implode(' + ', $aDepsWithIcons);
|
||||
$aUnresolvedModulesInfo[$sModuleId] = ['module' => $aModule, 'dependencies' => $aDepsWithIcons];
|
||||
}
|
||||
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
|
||||
$oException = new MissingDependencyException($sMessage);
|
||||
$oException->aModulesInfo = $aUnresolvedModulesInfo;
|
||||
throw $oException;
|
||||
}
|
||||
|
||||
// Return the ordered list, so that the dependencies are met...
|
||||
$aResult = [];
|
||||
foreach ($aOrderedModules as $sId) {
|
||||
$aResult[$sId] = $aModules[$sId];
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is key as it sorts modules by their dependencies (topological sort).
|
||||
* Modules with less dependencies are first.
|
||||
* When module A depends from module B with same amount of dependencies, moduleB is first.
|
||||
* This order can deal with
|
||||
* - cyclic dependencies
|
||||
* - further versions of same module (name)
|
||||
*
|
||||
* @param array $aUnresolvedDependencyModules : dict of Module objects by moduleId key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function SortModulesByCountOfDepencenciesDescending(array &$aUnresolvedDependencyModules): void
|
||||
{
|
||||
$aCountDepsByModuleId = [];
|
||||
$aDependsOnModuleName = [];
|
||||
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
/** @var Module $oModule */
|
||||
$aDependsOnModuleName[$oModule->GetModuleName()] = [];
|
||||
}
|
||||
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
$iInDegreeCounter = 0;
|
||||
/** @var Module $oModule */
|
||||
$aUnresolvedDependencyModuleNames = $oModule->GetUnresolvedDependencyModuleNames();
|
||||
foreach ($aUnresolvedDependencyModuleNames as $sModuleName) {
|
||||
if (array_key_exists($sModuleName, $aDependsOnModuleName)) {
|
||||
$aDependsOnModuleName[$sModuleName][] = $sModuleId;
|
||||
$iInDegreeCounter++;
|
||||
}
|
||||
}
|
||||
//include all modules
|
||||
$iInDegreeCounterIncludingOutsideModules = count($oModule->GetUnresolvedDependencyModuleNames());
|
||||
$aCountDepsByModuleId[$sModuleId] = [$iInDegreeCounter, $iInDegreeCounterIncludingOutsideModules, $sModuleId];
|
||||
}
|
||||
|
||||
$aRes = [];
|
||||
while (count($aUnresolvedDependencyModules) > 0) {
|
||||
asort($aCountDepsByModuleId);
|
||||
|
||||
uasort($aCountDepsByModuleId, function (array $aDeps1, array $aDeps2) {
|
||||
//compare $iInDegreeCounter
|
||||
$res = $aDeps1[0] - $aDeps2[0];
|
||||
if ($res != 0) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
//compare $iInDegreeCounterIncludingOutsideModules
|
||||
$res = $aDeps1[1] - $aDeps2[1];
|
||||
if ($res != 0) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
//alphabetical order at least
|
||||
return strcmp($aDeps1[2], $aDeps2[2]);
|
||||
});
|
||||
|
||||
$bOneLoopAtLeast = false;
|
||||
foreach ($aCountDepsByModuleId as $sModuleId => $iInDegreeCounter) {
|
||||
$oModule = $aUnresolvedDependencyModules[$sModuleId];
|
||||
|
||||
if ($bOneLoopAtLeast && $iInDegreeCounter > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
unset($aUnresolvedDependencyModules[$sModuleId]);
|
||||
unset($aCountDepsByModuleId[$sModuleId]);
|
||||
|
||||
$aRes[$sModuleId] = $oModule;
|
||||
|
||||
//when 2 versions of the same module (name) below array has been removed already
|
||||
if (array_key_exists($oModule->GetModuleName(), $aDependsOnModuleName)) {
|
||||
foreach ($aDependsOnModuleName[$oModule->GetModuleName()] as $sModuleId2) {
|
||||
if (!array_key_exists($sModuleId2, $aCountDepsByModuleId)) {
|
||||
continue;
|
||||
}
|
||||
$aDepCount = $aCountDepsByModuleId[$sModuleId2];
|
||||
$iInDegreeCounter = $aDepCount[0] - 1;
|
||||
$iInDegreeCounterIncludingOutsideModules = $aDepCount[1];
|
||||
$aCountDepsByModuleId[$sModuleId2] = [$iInDegreeCounter, $iInDegreeCounterIncludingOutsideModules, $sModuleId2];
|
||||
}
|
||||
|
||||
unset($aDependsOnModuleName[$oModule->GetModuleName()]);
|
||||
}
|
||||
|
||||
$bOneLoopAtLeast = true;
|
||||
}
|
||||
}
|
||||
|
||||
$aUnresolvedDependencyModules = $aRes;
|
||||
}
|
||||
}
|
||||
150
setup/modulediscovery.class.inc.php
Normal file → Executable file
150
setup/modulediscovery.class.inc.php
Normal file → Executable file
@@ -21,10 +21,14 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
|
||||
require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php');
|
||||
require_once(__DIR__.'/moduledependency/moduledependencysort.class.inc.php');
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
|
||||
class MissingDependencyException extends CoreException
|
||||
{
|
||||
@@ -116,10 +120,6 @@ class ModuleDiscovery
|
||||
if (is_null($aArgs) || ! is_array($aArgs)) {
|
||||
throw new ModuleFileReaderException("Error parsing module file args", 0, null, $sFilePath);
|
||||
}
|
||||
if (!array_key_exists('itop_version', $aArgs)) {
|
||||
// Assume 1.0.2
|
||||
$aArgs['itop_version'] = '1.0.2';
|
||||
}
|
||||
foreach (array_keys(self::$m_aModuleArgs) as $sArgName) {
|
||||
if (!array_key_exists($sArgName, $aArgs)) {
|
||||
throw new Exception("Module '$sId': missing argument '$sArgName'");
|
||||
@@ -211,76 +211,23 @@ class ModuleDiscovery
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
* @return array
|
||||
* @throws \MissingDependencyException
|
||||
*/
|
||||
*/
|
||||
public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
// Order the modules to take into account their inter-dependencies
|
||||
$aDependencies = [];
|
||||
$aSelectedModules = [];
|
||||
foreach ($aModules as $sId => $aModule) {
|
||||
list($sModuleName, ) = self::GetModuleName($sId);
|
||||
if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) {
|
||||
$aDependencies[$sId] = $aModule['dependencies'];
|
||||
$aSelectedModules[$sModuleName] = true;
|
||||
}
|
||||
}
|
||||
ksort($aDependencies);
|
||||
$aOrderedModules = [];
|
||||
$iLoopCount = 0;
|
||||
while (($iLoopCount < count($aModules)) && (count($aDependencies) > 0)) {
|
||||
foreach ($aDependencies as $sId => $aRemainingDeps) {
|
||||
$bDependenciesSolved = true;
|
||||
foreach ($aRemainingDeps as $sDepId) {
|
||||
if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
|
||||
$bDependenciesSolved = false;
|
||||
}
|
||||
}
|
||||
if ($bDependenciesSolved) {
|
||||
$aOrderedModules[] = $sId;
|
||||
unset($aDependencies[$sId]);
|
||||
if (is_null($aModulesToLoad)) {
|
||||
$aFilteredModules = $aModules;
|
||||
} else {
|
||||
$aFilteredModules = [];
|
||||
foreach ($aModules as $sModuleId => $aModule) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
if (in_array($sModuleName, $aModulesToLoad)) {
|
||||
$aFilteredModules[$sModuleId] = $aModule;
|
||||
}
|
||||
}
|
||||
$iLoopCount++;
|
||||
}
|
||||
if ($bAbortOnMissingDependency && count($aDependencies) > 0) {
|
||||
$aModulesInfo = [];
|
||||
$aModuleDeps = [];
|
||||
foreach ($aDependencies as $sId => $aDeps) {
|
||||
$aModule = $aModules[$sId];
|
||||
$aDepsWithIcons = [];
|
||||
foreach ($aDeps as $sIndex => $sDepId) {
|
||||
if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
|
||||
$aDepsWithIcons[$sIndex] = '✅ '.$sDepId;
|
||||
} else {
|
||||
$aDepsWithIcons[$sIndex] = '❌ '.$sDepId;
|
||||
}
|
||||
}
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons);
|
||||
$aModulesInfo[$sId] = ['module' => $aModule, 'dependencies' => $aDepsWithIcons];
|
||||
}
|
||||
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
|
||||
$oException = new MissingDependencyException($sMessage);
|
||||
$oException->aModulesInfo = $aModulesInfo;
|
||||
throw $oException;
|
||||
}
|
||||
// Return the ordered list, so that the dependencies are met...
|
||||
$aResult = [];
|
||||
foreach ($aOrderedModules as $sId) {
|
||||
$aResult[$sId] = $aModules[$sId];
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the duplicate modules (i.e. modules with the same name but with a different version) from the supplied list of modules
|
||||
* @param array $aModules
|
||||
* @return array The ordered modules as a duplicate-free list of modules
|
||||
*/
|
||||
public static function RemoveDuplicateModules($aModules)
|
||||
{
|
||||
// No longer needed, kept only for compatibility
|
||||
// The de-duplication is now done directly by the AddModule method
|
||||
return $aModules;
|
||||
return ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aFilteredModules, $bAbortOnMissingDependency);
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
@@ -292,73 +239,6 @@ class ModuleDiscovery
|
||||
return static::$oPhpExpressionEvaluator;
|
||||
}
|
||||
|
||||
protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules)
|
||||
{
|
||||
$bResult = false;
|
||||
$aModuleVersions = [];
|
||||
// Separate the module names from their version for an easier comparison later
|
||||
foreach ($aOrderedModules as $sModuleId) {
|
||||
list($sModuleName, $sVersion) = self::GetModuleName($sModuleId);
|
||||
$aModuleVersions[$sModuleName] = $sVersion;
|
||||
}
|
||||
if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches)) {
|
||||
$aReplacements = [];
|
||||
$aPotentialPrerequisites = [];
|
||||
foreach ($aMatches as $aMatch) {
|
||||
foreach ($aMatch as $sModuleId) {
|
||||
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
|
||||
// where the operator is < <= = > >= (by default >=)
|
||||
$aModuleMatches = [];
|
||||
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
|
||||
$sModuleName = $aModuleMatches[1];
|
||||
$aPotentialPrerequisites[$sModuleName] = true;
|
||||
$sOperator = $aModuleMatches[2];
|
||||
if ($sOperator == '') {
|
||||
$sOperator = '>=';
|
||||
}
|
||||
$sExpectedVersion = $aModuleMatches[3];
|
||||
if (array_key_exists($sModuleName, $aModuleVersions)) {
|
||||
// module is present, check the version
|
||||
$sCurrentVersion = $aModuleVersions[$sModuleName];
|
||||
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
|
||||
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
} else {
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
} else {
|
||||
// module is not present
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$bMissingPrerequisite = false;
|
||||
foreach (array_keys($aPotentialPrerequisites) as $sModuleName) {
|
||||
if (array_key_exists($sModuleName, $aSelectedModules)) {
|
||||
// This module is actually a prerequisite
|
||||
if (!array_key_exists($sModuleName, $aModuleVersions)) {
|
||||
$bMissingPrerequisite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($bMissingPrerequisite) {
|
||||
$bResult = false;
|
||||
} else {
|
||||
$sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $sDepString);
|
||||
try {
|
||||
$bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr);
|
||||
} catch (ModuleFileReaderException $e) {
|
||||
//logged already
|
||||
echo "Failed to parse the boolean Expression = '$sBooleanExpr'<br/>";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search (on the disk) for all defined iTop modules, load them and returns the list (as an array)
|
||||
* of the possible iTop modules to install
|
||||
|
||||
@@ -89,6 +89,7 @@ class ExtensionInstallation extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeString("source", ["allowed_values" => null, "sql" => "source", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("uninstallable", ["allowed_values" => new ValueSetEnum('yes,no,maybe'), "sql" => "uninstallable", "default_value" => 'yes', "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", ["allowed_values" => null, "sql" => "installed", "default_value" => 'NOW()', "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeText("description", ["allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => true, "depends_on" => []]));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', ['code', 'label', 'version', 'installed', 'source']); // Attributes to be displayed for the complete details
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1555,17 +1555,8 @@ JS
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WizardController $oWizard
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if ommitted
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function AnalyzeInstallation($oWizard, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
public static function GetConfig($oWizard)
|
||||
{
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
$oConfig = new Config();
|
||||
$sSourceDir = $oWizard->GetParameter('source_dir', '');
|
||||
|
||||
@@ -1580,7 +1571,25 @@ JS
|
||||
$aParamValues = $oWizard->GetParamForConfigArray();
|
||||
$aParamValues['source_dir'] = $sRelativeSourceDir;
|
||||
$oConfig->UpdateFromParams($aParamValues, null);
|
||||
$aDirsToScan = [$sSourceDir];
|
||||
|
||||
return $oConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WizardController $oWizard
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if ommitted
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function AnalyzeInstallation($oWizard, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
|
||||
$oConfig = self::GetConfig($oWizard);
|
||||
|
||||
$aDirsToScan = [$oWizard->GetParameter('source_dir', '')];
|
||||
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToScan[] = APPROOT.'extensions';
|
||||
|
||||
@@ -79,7 +79,7 @@ class InstallationFileService
|
||||
public function GetItopExtensionsMap(): ItopExtensionsMap
|
||||
{
|
||||
if (is_null($this->oItopExtensionsMap)) {
|
||||
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment, true);
|
||||
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment);
|
||||
}
|
||||
return $this->oItopExtensionsMap;
|
||||
}
|
||||
|
||||
@@ -210,12 +210,12 @@ HTML;
|
||||
}
|
||||
}
|
||||
$oPage->LinkScriptFromAppRoot('setup/setup.js');
|
||||
$oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n");
|
||||
$oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n");
|
||||
$oPage->add('<form id="wiz_form" class="ibo-setup--wizard" method="post">');
|
||||
$oPage->add('<div class="ibo-setup--wizard--content">');
|
||||
$oStep->Display($oPage);
|
||||
$oPage->add('</div>');
|
||||
$oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n");
|
||||
$oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n");
|
||||
|
||||
// Add the back / next buttons and the hidden form
|
||||
// to store the parameters
|
||||
|
||||
@@ -50,6 +50,7 @@ require_once(APPROOT.'setup/applicationinstaller.class.inc.php');
|
||||
require_once(APPROOT.'setup/parameters.class.inc.php');
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
require_once(APPROOT.'setup/extensionsmap.class.inc.php');
|
||||
require_once APPROOT.'setup/feature_removal/SetupAudit.php';
|
||||
|
||||
/**
|
||||
* First step of the iTop Installation Wizard: Welcome screen, requirements
|
||||
@@ -1310,14 +1311,16 @@ EOF
|
||||
*/
|
||||
class WizStepModulesChoice extends WizardStep
|
||||
{
|
||||
protected static $SEP = '_';
|
||||
protected $bUpgrade = false;
|
||||
protected static string $SEP = '_';
|
||||
protected bool $bUpgrade = false;
|
||||
protected bool $bCanMoveForward = true;
|
||||
protected ?Config $oConfig = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var iTopExtensionsMap
|
||||
*/
|
||||
protected $oExtensionsMap;
|
||||
protected iTopExtensionsMap $oExtensionsMap;
|
||||
|
||||
protected PhpExpressionEvaluator $oPhpExpressionEvaluator;
|
||||
|
||||
@@ -1325,7 +1328,7 @@ class WizStepModulesChoice extends WizardStep
|
||||
* Whether we were able to load the choices from the database or not
|
||||
* @var bool
|
||||
*/
|
||||
protected $bChoicesFromDatabase;
|
||||
protected bool $bChoicesFromDatabase;
|
||||
|
||||
public function __construct(WizardController $oWizard, $sCurrentState)
|
||||
{
|
||||
@@ -1343,12 +1346,13 @@ class WizStepModulesChoice extends WizardStep
|
||||
// only called if the config file exists : we are updating a previous installation !
|
||||
// WARNING : we can't load this config directly, as it might be from another directory with a different approot_url (N°2684)
|
||||
if ($sConfigPath !== null) {
|
||||
$oConfig = new Config($sConfigPath);
|
||||
$this->oConfig = new Config($sConfigPath);
|
||||
|
||||
$aParamValues = $oWizard->GetParamForConfigArray();
|
||||
$oConfig->UpdateFromParams($aParamValues);
|
||||
$this->oConfig->UpdateFromParams($aParamValues);
|
||||
|
||||
$this->bChoicesFromDatabase = $this->oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($this->oConfig);
|
||||
$this->bChoicesFromDatabase = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1364,6 +1368,52 @@ class WizStepModulesChoice extends WizardStep
|
||||
return ['WizStepModulesChoice', 'WizStepSummary'];
|
||||
}
|
||||
|
||||
public function GetAddedAndRemovedExtensions($aSelectedExtensions)
|
||||
{
|
||||
$aExtensionsAdded = [];
|
||||
$aExtensionsRemoved = [];
|
||||
$aExtensionsNotUninstallable = [];
|
||||
foreach ($this->oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
/* @var \iTopExtension $oExtension */
|
||||
$bSelected = in_array($oExtension->sCode, $aSelectedExtensions);
|
||||
if ($oExtension->bInstalled && !$bSelected) {
|
||||
$aExtensionsRemoved[$oExtension->sCode] = $oExtension->sLabel;
|
||||
if (!$oExtension->CanBeUninstalled()) {
|
||||
$aExtensionsNotUninstallable[$oExtension->sCode] = true;
|
||||
}
|
||||
} elseif (!$oExtension->bInstalled && $bSelected) {
|
||||
$aExtensionsAdded[$oExtension->sCode] = $oExtension->sLabel;
|
||||
}
|
||||
}
|
||||
|
||||
$sExtensionsAdded = '';
|
||||
if (count($aExtensionsAdded) > 0) {
|
||||
$sExtensionsAdded = '<ul>';
|
||||
foreach ($aExtensionsAdded as $sExtensionCode) {
|
||||
$sExtensionsAdded .= '<li>'.$sExtensionCode.'</li>';
|
||||
}
|
||||
$sExtensionsAdded .= '</ul>';
|
||||
} else {
|
||||
$sExtensionsAdded = '<ul><li>No extension added.</li></ul>';
|
||||
}
|
||||
|
||||
$sExtensionsRemoved = '';
|
||||
if (count($aExtensionsRemoved) > 0) {
|
||||
$sExtensionsRemoved = '<ul>';
|
||||
foreach ($aExtensionsRemoved as $sCode => $sExtensionCode) {
|
||||
$sForcedUninstall = '';
|
||||
if (isset($aExtensionsNotUninstallable[$sCode])) {
|
||||
$sForcedUninstall = ' (forced uninstallation)';
|
||||
}
|
||||
$sExtensionsRemoved .= '<li>'.$sExtensionCode.$sForcedUninstall.'</li>';
|
||||
}
|
||||
$sExtensionsRemoved .= '</ul>';
|
||||
} else {
|
||||
$sExtensionsRemoved = '<ul><li>No extension removed.</li></ul>';
|
||||
}
|
||||
return [$sExtensionsAdded, $sExtensionsRemoved];
|
||||
}
|
||||
|
||||
public function ProcessParams($bMoveForward = true)
|
||||
{
|
||||
// Accumulates the selected modules:
|
||||
@@ -1393,9 +1443,14 @@ class WizStepModulesChoice extends WizardStep
|
||||
if (class_exists('CreateITILProfilesInstaller')) {
|
||||
$this->oWizard->SetParameter('old_addon', true);
|
||||
}
|
||||
|
||||
[$sExtensionsAdded, $sExtensionsRemoved] = $this->GetAddedAndRemovedExtensions($aExtensions);
|
||||
|
||||
$this->oWizard->SetParameter('selected_modules', json_encode(array_keys($aModules)));
|
||||
$this->oWizard->SetParameter('selected_extensions', json_encode($aExtensions));
|
||||
$this->oWizard->SetParameter('display_choices', $sDisplayChoices);
|
||||
$this->oWizard->SetParameter('extensions_added', $sExtensionsAdded);
|
||||
$this->oWizard->SetParameter('extensions_removed', $sExtensionsRemoved);
|
||||
return ['class' => 'WizStepSummary', 'state' => ''];
|
||||
}
|
||||
|
||||
@@ -1452,7 +1507,7 @@ class WizStepModulesChoice extends WizardStep
|
||||
}
|
||||
$oPage->add('<img src="'.$sBannerUrl.'"/>');
|
||||
}
|
||||
$sDescription = isset($aStepInfo['description']) ? $aStepInfo['description'] : '';
|
||||
$sDescription = $aStepInfo['description'] ?? '';
|
||||
$oPage->add('<span>'.$sDescription.'</span>');
|
||||
$oPage->add('</div>');
|
||||
|
||||
@@ -1558,7 +1613,7 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
$aAlternatives = isset($aInfo['alternatives']) ? $aInfo['alternatives'] : [];
|
||||
$aAlternatives = $aInfo['alternatives'] ?? [];
|
||||
$sChoiceName = null;
|
||||
foreach ($aAlternatives as $index => $aChoice) {
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
@@ -1602,7 +1657,7 @@ EOF
|
||||
if ($this->bUpgrade) {
|
||||
// In upgrade mode, the defaults are the installed modules
|
||||
foreach ($aChoice['modules'] as $sModuleId) {
|
||||
if ($aModules[$sModuleId]['version_db'] != '') {
|
||||
if ($aModules[$sModuleId]['installed_version'] != '') {
|
||||
// A module corresponding to this choice is installed
|
||||
$aScores[$sChoiceId][$sModuleId] = true;
|
||||
}
|
||||
@@ -1660,7 +1715,7 @@ EOF
|
||||
}
|
||||
if (array_key_exists('modules', $aChoice)) {
|
||||
foreach ($aChoice['modules'] as $sModuleId) {
|
||||
if ($aModules[$sModuleId]['version_db'] != '') {
|
||||
if ($aModules[$sModuleId]['installed_version'] != '') {
|
||||
// A module corresponding to this choice is installed, increase the score of this choice
|
||||
if (!isset($aScores[$sChoiceId])) {
|
||||
$aScores[$sChoiceId] = [];
|
||||
@@ -1846,6 +1901,7 @@ EOF
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
|
||||
protected function GetStepInfo($idx = null)
|
||||
{
|
||||
$aStepInfo = null;
|
||||
@@ -1866,12 +1922,12 @@ EOF
|
||||
// Additional step for the "extensions"
|
||||
$aStepDefinition = [
|
||||
'title' => 'Extensions',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.</h2>',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.</h2>',
|
||||
'banner' => '/images/icons/icons8-puzzle.svg',
|
||||
'options' => [],
|
||||
];
|
||||
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
foreach ($this->oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
if (($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) && ($oExtension->bVisible) && (count($oExtension->aMissingDependencies) == 0)) {
|
||||
$aStepDefinition['options'][] = [
|
||||
'extension_code' => $oExtension->sCode,
|
||||
@@ -1882,16 +1938,19 @@ EOF
|
||||
'modules' => $oExtension->aModules,
|
||||
'mandatory' => $oExtension->bMandatory || ($oExtension->sSource === iTopExtension::SOURCE_REMOTE),
|
||||
'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
|
||||
'uninstallable' => $oExtension->CanBeUninstalled(),
|
||||
'missing' => $oExtension->bRemovedFromDisk,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Display this step of the wizard only if there is something to display
|
||||
if (count($aStepDefinition['options']) !== 0) {
|
||||
$aSteps[] = $aStepDefinition;
|
||||
$this->oWizard->SetParameter('additional_extensions_modules', json_encode($aStepDefinition['options']));
|
||||
}
|
||||
} else {
|
||||
// No wizard configuration provided, build a standard one with just one big list
|
||||
// No wizard configuration provided, build a standard one with just one big list. All items are mandatory, only works when there are no conflicted modules.
|
||||
$aStepDefinition = [
|
||||
'title' => 'Modules Selection',
|
||||
'description' => '<h2>Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.</h2>',
|
||||
@@ -1945,10 +2004,47 @@ EOF
|
||||
return '<i class="setup-extension--icon '.$sDecorationClass.'" data-tooltip-content="'.$sResult.'"></i>';
|
||||
}
|
||||
|
||||
public function ComputeChoiceFlags(array $aChoice, string $sChoiceId, array $aSelectedComponents, bool $bAllDisabled, bool $bDisableUninstallCheck, bool $bUpgradeMode)
|
||||
{
|
||||
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
|
||||
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] === true || $aChoice['uninstallable'] === 'yes' : $oITopExtension->CanBeUninstalled();
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $bUpgradeMode && $oITopExtension->bInstalled && !$bCanBeUninstalled && !$bDisableUninstallCheck;
|
||||
|
||||
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
|
||||
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
|
||||
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
|
||||
$bChecked = $bMandatory || $bSelected;
|
||||
|
||||
if (isset($aChoice['sub_options'])) {
|
||||
$aOptions = $aChoice['sub_options']['options'] ?? [];
|
||||
foreach ($aOptions as $index => $aSubChoice) {
|
||||
$sSubChoiceId = $sChoiceId.self::$SEP.$index;
|
||||
$aSubFlags = $this->ComputeChoiceFlags($aSubChoice, $sSubChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $bUpgradeMode);
|
||||
if ($aSubFlags['checked']) {
|
||||
$bChecked = true;
|
||||
if ($aSubFlags['disabled']) {
|
||||
//If some sub options are enabled and cannot be disabled, this choice should also cannot be disabled since it would disable all its sub options
|
||||
$bDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'uninstallable' => $bCanBeUninstalled,
|
||||
'missing' => $bMissingFromDisk,
|
||||
'installed' => $bInstalled,
|
||||
'disabled' => $bDisabled,
|
||||
'checked' => $bChecked,
|
||||
];
|
||||
}
|
||||
|
||||
protected function DisplayOptions($oPage, $aStepInfo, $aSelectedComponents, $aDefaults, $sParentId = '', $bAllDisabled = false)
|
||||
{
|
||||
$aOptions = isset($aStepInfo['options']) ? $aStepInfo['options'] : [];
|
||||
$aAlternatives = isset($aStepInfo['alternatives']) ? $aStepInfo['alternatives'] : [];
|
||||
$aOptions = $aStepInfo['options'] ?? [];
|
||||
$aAlternatives = $aStepInfo['alternatives'] ?? [];
|
||||
|
||||
$bDisableUninstallCheck = (bool)$this->oWizard->GetParameter('force-uninstall', false);
|
||||
|
||||
@@ -1956,20 +2052,33 @@ EOF
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$bIsDefault = array_key_exists($sChoiceId, $aDefaults);
|
||||
$aFlags = static::ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade);
|
||||
|
||||
$bCanBeUninstalled = $this->oExtensionsMap->Get($aChoice['extension_code'])->CanBeUninstalled();
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $this->bUpgrade && $bIsDefault && !$bCanBeUninstalled && !$bDisableUninstallCheck;
|
||||
;
|
||||
$bDisabled = $bMandatory || $bAllDisabled;
|
||||
$bChecked = $bMandatory || $bSelected;
|
||||
$sChecked = $bChecked ? ' checked ' : '';
|
||||
$sDisabled = $bDisabled ? ' disabled data-disabled="disabled" ' : '';
|
||||
$sUnremovable = !$bCanBeUninstalled ? ' unremovable ' : '';
|
||||
$sHiddenInput = $bDisabled && $bChecked ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
|
||||
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.' ');
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $bCanBeUninstalled);
|
||||
$sTooltip = '';
|
||||
$sUnremovable = '';
|
||||
if ($aFlags['missing']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag removed">source removed</span>';
|
||||
}
|
||||
if ($aFlags['installed']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag checked installed">installed</span>';
|
||||
$sTooltip .= '<span class="setup-extension-tag unchecked tobeuninstalled">to be uninstalled</span>';
|
||||
} else {
|
||||
$sTooltip .= '<span class="setup-extension-tag checked tobeinstalled">to be installed</span>';
|
||||
$sTooltip .= '<span class="setup-extension-tag unchecked notinstalled">not installed</span>';
|
||||
}
|
||||
if (!$aFlags['uninstallable']) {
|
||||
$sTooltip .= '<span class="setup-extension-tag notuninstallable">cannot be uninstalled</span>';
|
||||
}
|
||||
if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) {
|
||||
$this->bCanMoveForward = false;//Disable "Next"
|
||||
}
|
||||
$sChecked = $aFlags['checked'] ? ' checked ' : '';
|
||||
$sDisabled = $aFlags['disabled'] ? ' disabled data-disabled="disabled" ' : '';
|
||||
$sMissingModule = $aFlags['missing'] ? 'setup-extension--missing' : '';
|
||||
|
||||
$sHiddenInput = $aFlags['disabled'] && $aFlags['checked'] ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
|
||||
$oPage->add('<div class="choice '.$sMissingModule.'" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.' ');
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $aFlags['disabled'], $sTooltip);
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
$sChoiceName = null;
|
||||
@@ -2004,7 +2113,6 @@ EOF
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
|
||||
if ($sChoiceName == null) {
|
||||
$sChoiceName = $sChoiceId; // All radios share the same name
|
||||
}
|
||||
@@ -2030,14 +2138,13 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $bUninstallable = true)
|
||||
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $sTooltip = '')
|
||||
{
|
||||
$sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '<a class="setup--wizard-choice--more-info" target="_blank" href="'.$aChoice['more_info'].'">More information</a>' : '';
|
||||
$sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : '';
|
||||
$sSourceLabel = $aChoice['source_label'] ?? '';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$sUninstallationWarning = $bUninstallable ? '' : '<span style="color:orangered" title="Once this extension has been installed, it cannot be removed">(!)</span>';
|
||||
|
||||
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.'</label> '.$sUninstallationWarning.' '.$sMoreInfo.'');
|
||||
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.' '.$sTooltip.'</label> '.$sMoreInfo.'');
|
||||
$sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : '';
|
||||
$oPage->add('<div class="setup--wizard-choice--description description">'.$sDescription.'<span id="sub_choices'.$sId.'">');
|
||||
if (isset($aChoice['sub_options'])) {
|
||||
@@ -2052,6 +2159,22 @@ EOF
|
||||
return $sSourceDir.'/installation.xml';
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
|
||||
return $this->bCanMoveForward ? 'return true;' : 'return false;';
|
||||
}
|
||||
|
||||
public function GetNextButtonLabel()
|
||||
{
|
||||
return $this->bCanMoveForward ? 'Next' : 'Non-uninstallable extension missing';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2069,6 +2192,18 @@ class WizStepSummary extends WizardStep
|
||||
$this->bDependencyCheck = true;
|
||||
try {
|
||||
SetupUtils::AnalyzeInstallation($this->oWizard, true, $aSelectedModules);
|
||||
|
||||
/*$sInstallMode = utils::ReadParam('install_mode');
|
||||
\SetupLog::Info(__METHOD__, null, ['$sInstallMode' => $sInstallMode]);
|
||||
//if ($sInstallMode === "upgrade") {
|
||||
$aExtensions = json_decode($this->oWizard->GetParameter('selected_extensions'), true);
|
||||
$oSetupAudit = new SetupAudit([]);
|
||||
|
||||
$oConfig = SetupUtils::GetConfig($this->oWizard);
|
||||
$oSetupAudit->SetSelectedExtensions($oConfig, $aExtensions);
|
||||
//$oSetupAudit->AuditExtensionsCleanupRules(true);
|
||||
//}
|
||||
*/
|
||||
} catch (MissingDependencyException $e) {
|
||||
$this->bDependencyCheck = false;
|
||||
$this->sDependencyIssue = $e->getHtmlDesc();
|
||||
@@ -2133,6 +2268,14 @@ class WizStepSummary extends WizardStep
|
||||
|
||||
$oPage->add('<fieldset id="summary"><legend>Installation Parameters</legend>');
|
||||
$oPage->add('<div id="params_summary">');
|
||||
|
||||
$oPage->add('<div class="closed"><span class="title ibo-setup-summary-title">Extensions to be installed</span>');
|
||||
$oPage->add($this->oWizard->GetParameter('extensions_added'));
|
||||
$oPage->add('</div>');
|
||||
$oPage->add('<div class="closed"><span class="title ibo-setup-summary-title">Extensions to be uninstalled</span>');
|
||||
$oPage->add($this->oWizard->GetParameter('extensions_removed'));
|
||||
$oPage->add('</div>');
|
||||
|
||||
$oPage->add('<div class="closed"><span class="title ibo-setup-summary-title">Database Parameters</span><ul>');
|
||||
$oPage->add('<li>Server Name: '.$aInstallParams['database']['server'].'</li>');
|
||||
$oPage->add('<li>DB User Name: '.$aInstallParams['database']['user'].'</li>');
|
||||
|
||||
@@ -1019,10 +1019,10 @@ EOF
|
||||
if ($sModuleId == '_Root_') {
|
||||
continue;
|
||||
}
|
||||
if ($aModuleData['version_db'] == '') {
|
||||
if ($aModuleData['installed_version'] == '') {
|
||||
continue;
|
||||
}
|
||||
$oPage->add('InstalledModule/'.$sModuleId.': '.$aModuleData['version_db']."\n");
|
||||
$oPage->add('InstalledModule/'.$sModuleId.': '.$aModuleData['installed_version']."\n");
|
||||
}
|
||||
|
||||
$oPage->add('===== end =====');
|
||||
|
||||
@@ -2747,7 +2747,7 @@ class SynchroReplica extends DBObject implements iDisplay
|
||||
$aRows = [];
|
||||
foreach ($aData as $sKey => $value) {
|
||||
if (strpos(CMDBSource::GetFieldType($sSQLTable, $sKey), 'blob') !== false) {
|
||||
$aRows[] = ['attcode' => $sKey, 'data' => sprintf('<i>%s (%s)</i>', Dict::S('Core:AttributeBlob'), utils::BytesToFriendlyFormat(strlen($value)))];
|
||||
$aRows[] = ['attcode' => $sKey, 'data' => sprintf('<i>%s (%s)</i>', Dict::S('Core:AttributeBlob'), utils::BytesToFriendlyFormat(utils::StrLen($value)))];
|
||||
} else {
|
||||
$aRows[] = ['attcode' => $sKey, 'data' => utils::EscapeHtml($value)];
|
||||
}
|
||||
|
||||
129
tests/php-static-analysis/README.md
Normal file
129
tests/php-static-analysis/README.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# PHP static analysis
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Usages](#usages)
|
||||
- [Analysing a package](#analysing-a-package)
|
||||
- [Analysing a module](#analysing-a-module)
|
||||
- [Configuration](#configuration)
|
||||
- [Adjust local configuration to your needs](#adjust-local-configuration-to-your-needs)
|
||||
- [Adjust configuration for a particular CI repository / job](#adjust-configuration-for-a-particular-ci-repository--job)
|
||||
|
||||
## Installation
|
||||
- Install dependencies by running `composer install` in this folder
|
||||
- You should be all set! 🚀
|
||||
|
||||
## Usages
|
||||
### Analysing a package
|
||||
_Do this if you want to analyse the whole iTop package (iTop core, extensions, third-party libs, ...)_
|
||||
|
||||
- Make sure you ran a setup on your iTop as it will analyse the `env-production` folder
|
||||
- Open a prompt in your iTop folder
|
||||
- Run the following command
|
||||
```bash
|
||||
tests/php-static-analysis/vendor/bin/phpstan analyse \
|
||||
--configuration ./tests/php-static-analysis/config/for-package.dist.neon \
|
||||
--error-format raw
|
||||
```
|
||||
|
||||
You will then have an output like this listing all errors:
|
||||
```bash
|
||||
tests/php-static-analysis/vendor/bin/phpstan analyse \
|
||||
--configuration ./tests/php-static-analysis/config/for-package.dist.neon \
|
||||
--error-format raw
|
||||
|
||||
1049/1049 [============================] 100%
|
||||
|
||||
<ITOP>\addons\userrights\userrightsprofile.class.inc.php:552:Call to static method InitSharedClassProperties() on an unknown class SharedObject.
|
||||
<ITOP>\addons\userrights\userrightsprofile.db.class.inc.php:927:Call to static method GetSharedClassProperties() on an unknown class SharedObject.
|
||||
<ITOP>\addons\userrights\userrightsprojection.class.inc.php:722:Access to an undefined property UserRightsProjection::$m_aClassProjs.
|
||||
<ITOP>\application\applicationextension.inc.php:295:Method AbstractPreferencesExtension::ApplyPreferences() should return bool but return statement is missing.
|
||||
<ITOP>\application\cmdbabstract.class.inc.php:1010:Class utils referenced with incorrect case: Utils.
|
||||
[...]
|
||||
```
|
||||
|
||||
### Analysing a module
|
||||
_Do this if you only want to analyse one or more modules within this iTop but not the whole package_
|
||||
|
||||
- Make sure you ran a setup on your iTop as it will analyse the `env-production` folder
|
||||
- Open a prompt in your iTop folder
|
||||
- Run the following command
|
||||
```
|
||||
tests/php-static-analysis/vendor/bin/phpstan analyse \
|
||||
--configuration ./tests/php-static-analysis/config/for-module.dist.neon \
|
||||
--error-format raw \
|
||||
env-production/<MODULE_CODE_1> [env-production/<MODULE_CODE_2> ...]
|
||||
```
|
||||
|
||||
You will then have an output like this listing all errors:
|
||||
```
|
||||
tests/php-static-analysis/vendor/bin/phpstan analyse \
|
||||
--configuration ./tests/php-static-analysis/config/for-module.dist.neon \
|
||||
--error-format raw \
|
||||
env-production/authent-ldap env-production/itop-oauth-client
|
||||
|
||||
49/49 [============================] 100%
|
||||
|
||||
<ITOP>\env-production\authent-ldap\model.authent-ldap.php:79:Undefined variable: $hDS
|
||||
<ITOP>\env-production\authent-ldap\model.authent-ldap.php:80:Undefined variable: $name
|
||||
<ITOP>\env-production\authent-ldap\model.authent-ldap.php:80:Undefined variable: $value
|
||||
<ITOP>\env-production\itop-oauth-client\vendor\composer\InstalledVersions.php:105:Parameter $parser of method Composer\InstalledVersions::satisfies() has invalid type Composer\Semver\VersionParser.
|
||||
[...]
|
||||
```
|
||||
|
||||
## Configuration
|
||||
### Adjust local configuration to your needs
|
||||
#### Define which PHP version to run the analysis for
|
||||
The way we configured PHPStan in this project changes how it will find the PHP version to run the analysis for. \
|
||||
By default PHPStan check the information from the composer.json file, but we changed that (via the `config/php-includes/set-php-version-from-process.php` include) so it used the PHP
|
||||
version currently ran by the CLI.
|
||||
|
||||
So all you have to do is either:
|
||||
- Prepend your command line with the path of the executable of the desired PHP version
|
||||
- Change the default PHP interpreter in your IDE settings
|
||||
|
||||
#### Change some parameters for a local run
|
||||
If you want to change some particular settings (eg. the memory limit, the rules level, ...) for a local run of the analysis you have 2 choices.
|
||||
|
||||
##### Method 1: CLI parameter
|
||||
For most parameters there is a good chance you can just add the parameter and its value in your command, which will override the one defined in the configuration file. \
|
||||
Below are some example, but your can find the complete reference [here](https://phpstan.org/user-guide/command-line-usage).
|
||||
|
||||
```bash
|
||||
--memory-limit 1G
|
||||
--level 5
|
||||
--error-format raw
|
||||
[...]
|
||||
```
|
||||
|
||||
**Pros** Quick and easy to try different parameters \
|
||||
**Cons** Parameters aren't saved, so you'll have to remember them and put them again next time
|
||||
|
||||
##### Method 2: Configuration file
|
||||
Crafting your own configuration file gives you the ability to fine tune any parameters, it's way more powerful but can also quickly lead to crashes if you mess with the symbols discovery (classes, ...). \
|
||||
But mostly it can be saved, shared, re-used; which is it's main purpose.
|
||||
|
||||
It is recommended that you create your configuration file from scratch and that you include the `base.dist.neon` so you are bootstrapped for the symbols discovery. Then you can override any parameter. \
|
||||
Check [the documentation](https://phpstan.org/config-reference#multiple-files) for more information.
|
||||
|
||||
```neon
|
||||
includes:
|
||||
- base.dist.neon
|
||||
|
||||
parameters:
|
||||
# Override parameters here
|
||||
```
|
||||
|
||||
#### Analyse only one (or some) folder(s) quicker
|
||||
It's pretty easy and good news you don't need to create a new configuration file or change an existing one. \
|
||||
Just adapt and use command lines from the [usages section](#usages) and add the folders you want to analyse at the end of the command, exactly like when analysing modules.
|
||||
|
||||
For example if you want to analyse just `<ITOP>/setup` and `<ITOP>/sources`, use something like:
|
||||
```
|
||||
tests/php-static-analysis/vendor/bin/phpstan analyse \
|
||||
--configuration ./tests/php-static-analysis/config/for-package.dist.neon \
|
||||
--error-format raw \
|
||||
setup sources
|
||||
```
|
||||
|
||||
### Adjust configuration for a particular CI repository / job
|
||||
TODO
|
||||
5
tests/php-static-analysis/composer.json
Normal file
5
tests/php-static-analysis/composer.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"phpstan/phpstan": "^2.1"
|
||||
}
|
||||
}
|
||||
72
tests/php-static-analysis/composer.lock
generated
Normal file
72
tests/php-static-analysis/composer.lock
generated
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "cc6d7580a5e98236d68d8b91de9ddebb",
|
||||
"packages": [
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.33",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
},
|
||||
"bin": [
|
||||
"phpstan",
|
||||
"phpstan.phar"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPStan - PHP Static Analysis Tool",
|
||||
"keywords": [
|
||||
"dev",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://phpstan.org/user-guide/getting-started",
|
||||
"forum": "https://github.com/phpstan/phpstan/discussions",
|
||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||
"security": "https://github.com/phpstan/phpstan/security/policy",
|
||||
"source": "https://github.com/phpstan/phpstan-src"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/ondrejmirtes",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/phpstan",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-05T10:24:31+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
29
tests/php-static-analysis/config/README.md
Normal file
29
tests/php-static-analysis/config/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## Disclaimer
|
||||
DON'T modify the following files without knowledge and discussing with the team:
|
||||
- base.dist.neon
|
||||
- for-package.dist.neon
|
||||
- for-module.dist.neon
|
||||
|
||||
## Purpose of these files
|
||||
### base.dist.neon
|
||||
This configuration file contains the common parameters for all analysis, whereas it is a package, a module or something specific. Among others:
|
||||
- Rules level for analysis
|
||||
- PHP version to compare
|
||||
- Necessary files for autoloaders discovery and such
|
||||
- ...
|
||||
|
||||
This file should not be modified for your specific needs, you should always include it and override the desired parameters. \
|
||||
See how it is done in `for-package.dist.neon` and `for-module.dist.neon` or on the documentation [here](https://phpstan.org/config-reference#multiple-files).
|
||||
|
||||
### for-package.dist.neon
|
||||
This configuration file contains the parameters to analyse a package (iTop core, modules, third-party libs).
|
||||
|
||||
### for-module.dist.neon
|
||||
This configuration file contains the parameters to analyse one or more modules only.
|
||||
|
||||
## How / when can I modify these files?
|
||||
**You CAN'T!** \
|
||||
Well, unless there is a good reason and you talked about it with the team. But you should never modify them for a specific need on your local environment.
|
||||
|
||||
- If you have a particular need for your local environment (eg. increase memory limit, change rules levels, analyse only a specific folder), check the [Configuration section](../#configuration) of the main README.md.
|
||||
- If you feel like there is need for an adjustment in the default configurations, discuss it with th team and make a PR.
|
||||
40
tests/php-static-analysis/config/base.dist.neon
Normal file
40
tests/php-static-analysis/config/base.dist.neon
Normal file
@@ -0,0 +1,40 @@
|
||||
includes:
|
||||
- php-includes/set-php-version-from-process.php # Workaround to set PHP version to the on running the CLI
|
||||
# for an explanation of the baseline concept, see: https://phpstan.org/user-guide/baseline
|
||||
#baseline HERE DO NOT REMOVE FOR CI
|
||||
|
||||
parameters:
|
||||
level: 0
|
||||
#phpVersion: null # Explicitly commented as we rather use the detected version from the above include (`php-includes/target-php-version.php`)
|
||||
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%' # Open in PHPStorm as it's Combodo's default IDE
|
||||
bootstrapFiles:
|
||||
- ../../../approot.inc.php
|
||||
- ../../../bootstrap.inc.php
|
||||
|
||||
scanFiles:
|
||||
# Files necessary as they contain some declarations (constants, classes, functions, ...)
|
||||
- ../../../approot.inc.php
|
||||
- ../../../bootstrap.inc.php
|
||||
excludePaths:
|
||||
analyse:
|
||||
# For third-party libs we should analyse them in a dedicated configuration as we can't improve / clean them which would
|
||||
# prevent us from raising the rules level as we improve / clean our codebase
|
||||
- ../../../lib # Irrelevant as we only want to analyze our codebase
|
||||
- ../../../node_modules # Irrelevant as we only want to analyze our codebase
|
||||
analyseAndScan:
|
||||
# This file generates "unignorable errors" for the baseline due to its format, so we don't have any other choice than to exclude it.
|
||||
# But mind that it will prevent PHPStan from warning us about PHP syntax errors in this file.
|
||||
- ../../../core/oql/build/PHP/Lempar.php
|
||||
|
||||
#- ../../../data # Left and commented on purpose to show that we want to analyse the generated cache files
|
||||
|
||||
# Note 1: We can't analyse these folders as if a PHP file requires another PHP element declared in an XML file, it won't find it. So we rely only on `env-production`
|
||||
# Note 2: Only the options selected during the setup will be analysed correctly in `env-production`. For unselected options, we still want to ignore them during the analysis as they would only give a false sentiment of security as their XML PHP classes / snippets / etc would not be tested.
|
||||
- ../../../data/production-modules (?) # Irrelevent as it will already be in `env-production` (for local run only, not useful in the CI)
|
||||
- ../../../datamodels # Irrelevent as it will already be in `env-production`
|
||||
- ../../../extensions # Irrelevent as it will already be in `env-production` (for local run only, not useful in the CI)
|
||||
- ../../../env-php-unit-tests (?) # Irrelevant as it will either already be in `env-production` or might be desynchronized from `env-production`
|
||||
- ../../../env-toolkit (?) # Irrelevent as it will either already be in `env-production` or might be desynchronized from `env-production` (for local run only, not useful in the CI)
|
||||
|
||||
- ../../../tests (?) # Exclude tests for now
|
||||
- ../../../toolkit (?) # Exlclude toolkit for now
|
||||
15
tests/php-static-analysis/config/for-module.dist.neon
Normal file
15
tests/php-static-analysis/config/for-module.dist.neon
Normal file
@@ -0,0 +1,15 @@
|
||||
includes:
|
||||
- base.dist.neon
|
||||
|
||||
parameters:
|
||||
paths:
|
||||
# We just want to analyse the module folder(s), either:
|
||||
# - Create your own `for-module.neon` file, include this one and override this parameter (see https://phpstan.org/config-reference#multiple-files)
|
||||
# - Pass the module folder(s) in the commande line (see https://phpstan.org/config-reference#analysed-files)
|
||||
scanDirectories:
|
||||
# Unlike for `for-package.dist.neon`, here we need to scan all the folders to discover symbols, but we only want to analyse the module folder.
|
||||
# We initially thought of doing it through the `excludePaths` param. by excluding everything but the module folder, but it doesn't seem to be possible, because it uses the `fnmatch()` function.
|
||||
# As a workaround, we list here all the folders to scan.
|
||||
#
|
||||
# Scan the whole project and rely on the `excludePaths` param. to filter the unnecessary
|
||||
- ../../..
|
||||
7
tests/php-static-analysis/config/for-package.dist.neon
Normal file
7
tests/php-static-analysis/config/for-package.dist.neon
Normal file
@@ -0,0 +1,7 @@
|
||||
includes:
|
||||
- base.dist.neon
|
||||
|
||||
parameters:
|
||||
paths:
|
||||
# We want to analyse almost the whole project, so we do a negative selection between the `paths` and `excludePaths` (see base.dist.neon) parameters
|
||||
- ../../../
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is only here to allow setting a specific PHP version to run the analysis for without
|
||||
* having to explicitly set it in the .neon file. This is the best way we found so far.
|
||||
*
|
||||
* @link https://phpstan.org/config-reference#phpversion
|
||||
*
|
||||
* Usage: Uses the CLI PHP version by default, which would work fine for
|
||||
* - The CI as the docker image has the target PHP version in both CLI and web
|
||||
* - The developer's IDE as PHPStorm also has a default PHP version configured which can be changed on the fly
|
||||
*/
|
||||
|
||||
// Default PHP version to analyse is the one running in CLI
|
||||
$config = [];
|
||||
$config['parameters']['phpVersion'] = PHP_VERSION_ID;
|
||||
|
||||
return $config;
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
Documentation on creating and maintaining tests in iTop.
|
||||
|
||||
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### PHPUnit configuration file
|
||||
@@ -78,7 +75,8 @@ Example :
|
||||
$oTagData->DBDelete();
|
||||
```
|
||||
|
||||
Warning : when the condition is met the test is finished and following code will be ignored !
|
||||
> [!WARNING]
|
||||
> When the condition is met the test is finished and following code will be ignored !
|
||||
|
||||
Another way to do is using try/catch blocks, for example :
|
||||
```php
|
||||
|
||||
@@ -28,14 +28,14 @@ class AjaxPageTest extends ItopDataTestCase
|
||||
$iLastCompilation = filemtime(APPROOT.'env-production');
|
||||
|
||||
// When
|
||||
$sOutput = $this->CallItopUrl(
|
||||
"/pages/exec.php?exec_module=itop-hub-connector&exec_page=ajax.php",
|
||||
$sOutput = $this->CallItopUri(
|
||||
"pages/exec.php?exec_module=itop-hub-connector&exec_page=ajax.php",
|
||||
[
|
||||
'auth_user' => $sLogin,
|
||||
'auth_pwd' => self::AUTHENTICATION_PASSWORD,
|
||||
'operation' => "compile",
|
||||
'authent' => self::AUTHENTICATION_TOKEN,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
// Then
|
||||
@@ -53,26 +53,4 @@ class AjaxPageTest extends ItopDataTestCase
|
||||
clearstatcache();
|
||||
$this->assertGreaterThan($iLastCompilation, filemtime(APPROOT.'env-production'), 'The env-production directory should have been rebuilt');
|
||||
}
|
||||
|
||||
protected function CallItopUrl($sUri, ?array $aPostFields = null, bool $bXDebugEnabled = false)
|
||||
{
|
||||
$ch = curl_init();
|
||||
if ($bXDebugEnabled) {
|
||||
curl_setopt($ch, CURLOPT_COOKIE, 'XDEBUG_SESSION=phpstorm');
|
||||
}
|
||||
|
||||
$sUrl = \MetaModel::GetConfig()->Get('app_root_url')."/$sUri";
|
||||
var_dump($sUrl);
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
$sOutput = curl_exec($ch);
|
||||
//echo "$sUrl error code:".curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2024 Combodo SAS
|
||||
*
|
||||
@@ -24,19 +25,15 @@
|
||||
require_once('../../../approot.inc.php');
|
||||
require_once(APPROOT.'application/startup.inc.php');
|
||||
|
||||
|
||||
$sEnvironment = MetaModel::GetEnvironmentId();
|
||||
$aEntries = array();
|
||||
$aEntries = [];
|
||||
$aCacheUserData = apc_cache_info_compat();
|
||||
if (is_array($aCacheUserData) && isset($aCacheUserData['cache_list']))
|
||||
{
|
||||
if (is_array($aCacheUserData) && isset($aCacheUserData['cache_list'])) {
|
||||
$sPrefix = 'itop-'.$sEnvironment.'-query-cache-';
|
||||
|
||||
foreach($aCacheUserData['cache_list'] as $i => $aEntry)
|
||||
{
|
||||
foreach ($aCacheUserData['cache_list'] as $i => $aEntry) {
|
||||
$sEntryKey = array_key_exists('info', $aEntry) ? $aEntry['info'] : $aEntry['key'];
|
||||
if (strpos($sEntryKey, $sPrefix) === 0)
|
||||
{
|
||||
if (strpos($sEntryKey, $sPrefix) === 0) {
|
||||
$aEntries[] = $sEntryKey;
|
||||
}
|
||||
}
|
||||
@@ -44,52 +41,39 @@ if (is_array($aCacheUserData) && isset($aCacheUserData['cache_list']))
|
||||
|
||||
echo "<pre>";
|
||||
|
||||
if (empty($aEntries))
|
||||
{
|
||||
if (empty($aEntries)) {
|
||||
echo "No Data";
|
||||
return;
|
||||
}
|
||||
|
||||
$sKey = $aEntries[0];
|
||||
$result = apc_fetch($sKey);
|
||||
if (!is_object($result))
|
||||
{
|
||||
if (!is_object($result)) {
|
||||
return;
|
||||
}
|
||||
$oSQLQuery = $result;
|
||||
|
||||
echo "NB Tables before;NB Tables after;";
|
||||
foreach($oSQLQuery->m_aContextData as $sField => $oValue)
|
||||
{
|
||||
foreach ($oSQLQuery->m_aContextData as $sField => $oValue) {
|
||||
echo $sField.';';
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
sort($aEntries);
|
||||
|
||||
foreach($aEntries as $sKey)
|
||||
{
|
||||
foreach ($aEntries as $sKey) {
|
||||
$result = apc_fetch($sKey);
|
||||
if (is_object($result))
|
||||
{
|
||||
if (is_object($result)) {
|
||||
$oSQLQuery = $result;
|
||||
if (isset($oSQLQuery->m_aContextData))
|
||||
{
|
||||
if (isset($oSQLQuery->m_aContextData)) {
|
||||
echo $oSQLQuery->m_iOriginalTableCount.";".$oSQLQuery->CountTables().';';
|
||||
foreach($oSQLQuery->m_aContextData as $oValue)
|
||||
{
|
||||
if (is_array($oValue))
|
||||
{
|
||||
foreach ($oSQLQuery->m_aContextData as $oValue) {
|
||||
if (is_array($oValue)) {
|
||||
$sVal = json_encode($oValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (empty($oValue))
|
||||
{
|
||||
} else {
|
||||
if (empty($oValue)) {
|
||||
$sVal = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sVal = $oValue;
|
||||
}
|
||||
}
|
||||
@@ -101,4 +85,3 @@ foreach($aEntries as $sKey)
|
||||
}
|
||||
|
||||
echo "</pre>";
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<testsuite name="ModuleIntegration">
|
||||
<file>integration-tests/DictionariesConsistencyAfterSetupTest.php</file>
|
||||
<file>integration-tests/DictionariesConsistencyTest.php</file>
|
||||
<file>integration-tests/iTopModulesDependencyValidationServiceTest.php</file>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
|
||||
@@ -54,10 +54,20 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
*/
|
||||
abstract public function GetDatamodelDeltaAbsPath(): string;
|
||||
|
||||
/**
|
||||
* @return array<string, string> : dict extensions folders by their code
|
||||
*/
|
||||
public function GetAdditionalFeaturePaths(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::LoadRequiredItopFiles();
|
||||
$this->oEnvironment = new UnitTestRunTimeEnvironment('production', $this->GetTestEnvironment());
|
||||
if (is_null($this->oEnvironment)) {
|
||||
$this->oEnvironment = new UnitTestRunTimeEnvironment($this->GetTestEnvironment());
|
||||
}
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
@@ -123,7 +133,6 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
// Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once.
|
||||
// This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457
|
||||
if (false === $this->IsEnvironmentReady()) {
|
||||
|
||||
$this->debug("Preparing custom environment '$sTestEnv' with the following datamodel files:");
|
||||
foreach ($this->oEnvironment->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
|
||||
$this->debug(" - $sCustomDatamodelFile");
|
||||
@@ -155,24 +164,33 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
$oTestConfig->ChangeModulesPath($sSourceEnv, $sTestEnv);
|
||||
// - Switch DB name to a dedicated one so we don't mess with the original one
|
||||
$sTestEnvSanitizedForDBName = preg_replace('/[^\d\w]/', '', $sTestEnv);
|
||||
$oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName);
|
||||
$sPreviousDB = $oTestConfig->Get('db_name');
|
||||
$sNewDB = $sPreviousDB.'_'.$sTestEnvSanitizedForDBName;
|
||||
$oTestConfig->Set('db_name', $sNewDB);
|
||||
|
||||
// - Compile env. based on the existing 'production' env.
|
||||
$oEnvironment = new UnitTestRunTimeEnvironment($sSourceEnv, $sTestEnv);
|
||||
$oEnvironment->WriteConfigFileSafe($oTestConfig);
|
||||
$oEnvironment->CompileFrom($sSourceEnv);
|
||||
//$oEnvironment = new UnitTestRunTimeEnvironment($sSourceEnv, $sTestEnv);
|
||||
$this->oEnvironment->WriteConfigFileSafe($oTestConfig);
|
||||
$this->oEnvironment->CompileFrom($sSourceEnv);
|
||||
|
||||
// - Force re-creating a fresh DB
|
||||
CMDBSource::InitFromConfig($oTestConfig);
|
||||
if (CMDBSource::IsDB($oTestConfig->Get('db_name'))) {
|
||||
if (CMDBSource::IsDB($sNewDB)) {
|
||||
CMDBSource::DropDB();
|
||||
}
|
||||
CMDBSource::CreateDB($oTestConfig->Get('db_name'));
|
||||
CMDBSource::CreateDB($sNewDB);
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv);
|
||||
// N°7446 For some reason we need to create the DB schema before starting the MM, then only we can create the tables.
|
||||
MetaModel::DBCreate();
|
||||
|
||||
// Make sure that runtime environment is complete
|
||||
// RunTimeEnvironment::AnalyzeInstallation would not return core modules otherwise...
|
||||
CMDBSource::DropTable("priv_module_install");
|
||||
CMDBSource::Query("CREATE TABLE $sNewDB.priv_module_install SELECT * FROM $sPreviousDB.priv_module_install");
|
||||
|
||||
$this->debug("Custom environment '$sTestEnv' is ready!");
|
||||
} else {
|
||||
$this->debug("Custom environment '$sTestEnv' READY BUILT:");
|
||||
}
|
||||
|
||||
parent::PrepareEnvironment();
|
||||
|
||||
@@ -18,6 +18,7 @@ use ArchivedObjectException;
|
||||
use CMDBObject;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Config;
|
||||
use Contact;
|
||||
use CoreException;
|
||||
use CoreUnexpectedValue;
|
||||
@@ -70,6 +71,9 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
private $aCreatedObjects = [];
|
||||
private $aEventListeners = [];
|
||||
|
||||
protected ?string $sConfigTmpBackupFile = null;
|
||||
protected ?Config $oiTopConfig = null;
|
||||
|
||||
/**
|
||||
* @var bool When testing with silo, there are some cache we need to update on tearDown. Doing it all the time will cost too much, so it's opt-in !
|
||||
* @see tearDown
|
||||
@@ -124,6 +128,8 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
\IssueLog::Error($this->getName());
|
||||
|
||||
$this->PrepareEnvironment();
|
||||
|
||||
if (static::USE_TRANSACTION) {
|
||||
@@ -190,6 +196,8 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
|
||||
CMDBObject::SetCurrentChange(null);
|
||||
|
||||
$this->RestoreConfiguration();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
@@ -1517,4 +1525,35 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
$oObject->Set($sStopwatchAttCode, $oStopwatch);
|
||||
}
|
||||
|
||||
protected function BackupConfiguration(): void
|
||||
{
|
||||
$sConfigPath = MetaModel::GetConfig()->GetLoadedFile();
|
||||
clearstatcache();
|
||||
echo sprintf("rights via ls on %s:\n %s \n", $sConfigPath, exec("ls -al $sConfigPath"));
|
||||
$sFilePermOutput = substr(sprintf('%o', fileperms('/etc/passwd')), -4);
|
||||
echo sprintf("rights via fileperms on %s:\n %s \n", $sConfigPath, $sFilePermOutput);
|
||||
|
||||
$this->sConfigTmpBackupFile = tempnam(sys_get_temp_dir(), "config_");
|
||||
MetaModel::GetConfig()->WriteToFile($this->sConfigTmpBackupFile);
|
||||
$this->oiTopConfig = new Config($sConfigPath);
|
||||
}
|
||||
|
||||
protected function RestoreConfiguration(): void
|
||||
{
|
||||
if (is_null($this->sConfigTmpBackupFile) || ! is_file($this->sConfigTmpBackupFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($this->oiTopConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//put config back
|
||||
$sConfigPath = $this->oiTopConfig->GetLoadedFile();
|
||||
@chmod($sConfigPath, 0770);
|
||||
$oConfig = new Config($this->sConfigTmpBackupFile);
|
||||
$oConfig->WriteToFile($sConfigPath);
|
||||
@chmod($sConfigPath, 0440);
|
||||
@unlink($this->sConfigTmpBackupFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
|
||||
use CMDBSource;
|
||||
use DateTime;
|
||||
use DeprecatedCallsLog;
|
||||
use MySQLTransactionNotClosedException;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use ReflectionMethod;
|
||||
use SetupUtils;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
@@ -29,6 +28,7 @@ use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
abstract class ItopTestCase extends KernelTestCase
|
||||
{
|
||||
public const TEST_LOG_DIR = 'test';
|
||||
protected array $aFileToClean = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
@@ -37,7 +37,7 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
public const DISABLE_DEPRECATEDCALLSLOG_ERRORHANDLER = true;
|
||||
public static $DEBUG_UNIT_TEST = false;
|
||||
protected static $aBackupStaticProperties = [];
|
||||
|
||||
public ?array $aLastCurlGetInfo = null;
|
||||
/**
|
||||
* @link https://docs.phpunit.de/en/9.6/annotations.html#preserveglobalstate PHPUnit `preserveGlobalState` annotation documentation
|
||||
*
|
||||
@@ -175,6 +175,15 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
}
|
||||
throw new MySQLTransactionNotClosedException('Some DB transactions were opened but not closed ! Fix the code by adding ROLLBACK or COMMIT statements !', []);
|
||||
}
|
||||
|
||||
foreach ($this->aFileToClean as $sPath) {
|
||||
if (is_file($sPath)) {
|
||||
@unlink($sPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupUtils::tidydir($sPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -631,4 +640,62 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
fclose($handle);
|
||||
return array_reverse($aLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sUrl
|
||||
* @param array|null $aPostFields
|
||||
* @param array|null $aCurlOptions
|
||||
* @param $bXDebugEnabled
|
||||
* @return string
|
||||
*/
|
||||
protected function CallUrl($sUrl, ?array $aPostFields = [], ?array $aCurlOptions = [], $bXDebugEnabled = false): string
|
||||
{
|
||||
$ch = curl_init();
|
||||
if ($bXDebugEnabled) {
|
||||
curl_setopt($ch, CURLOPT_COOKIE, "XDEBUG_SESSION=phpstorm");
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt_array($ch, $aCurlOptions);
|
||||
if ($this->IsArrayOfArray($aPostFields)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($aPostFields));
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
}
|
||||
|
||||
$sOutput = curl_exec($ch);
|
||||
|
||||
$info = curl_getinfo($ch);
|
||||
$this->aLastCurlGetInfo = $info;
|
||||
$sErrorMsg = curl_error($ch);
|
||||
$iErrorCode = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
\IssueLog::Info(__METHOD__, null, ['url' => $sUrl, 'error' => $sErrorMsg, 'error_code' => $iErrorCode, 'post_fields' => $aPostFields, 'info' => $info]);
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
|
||||
private function IsArrayOfArray(array $aStruct): bool
|
||||
{
|
||||
foreach ($aStruct as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function CallItopUri(string $sUri, ?array $aPostFields = [], ?array $aCurlOptions = [], $bXDebugEnabled = false): string
|
||||
{
|
||||
$sUrl = \MetaModel::GetConfig()->Get('app_root_url')."/$sUri";
|
||||
|
||||
return $this->CallUrl($sUrl, $aPostFields, $aCurlOptions, $bXDebugEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,26 +26,25 @@ use utils;
|
||||
*/
|
||||
class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
/**
|
||||
* @var false
|
||||
*/
|
||||
public bool $bUseDelta = true;
|
||||
|
||||
/**
|
||||
* @var true
|
||||
*/
|
||||
public bool $bUseAdditionalFeatures = false;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $aCustomDatamodelFiles = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var string[]
|
||||
*/
|
||||
protected $sSourceEnv;
|
||||
|
||||
public function __construct($sSourceEnv, $sTargetEnv)
|
||||
{
|
||||
parent::__construct($sTargetEnv);
|
||||
$this->sSourceEnv = $sSourceEnv;
|
||||
}
|
||||
|
||||
public function GetEnvironment(): string
|
||||
{
|
||||
return $this->sFinalEnv;
|
||||
}
|
||||
protected $aAdditionExtensionFoldersByCode = null;
|
||||
|
||||
public function CompileFrom($sSourceEnv, $bUseSymLinks = null)
|
||||
{
|
||||
@@ -56,6 +55,15 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
|
||||
SetupUtils::copydir(APPROOT.'/data/'.$sSourceEnv.'-modules', $sDestModulesDir, $bUseSymLinks);
|
||||
|
||||
if ($this->bUseAdditionalFeatures) {
|
||||
foreach ($this->GetExtensionFoldersToAdd() as $sExtensionCode => $sFolderPath) {
|
||||
\SetupLog::Info("ExtensionFoldersToAdd: $sExtensionCode => $sFolderPath");
|
||||
$sFolderName = basename($sFolderPath);
|
||||
@mkdir($sDestModulesDir.DIRECTORY_SEPARATOR.$sFolderName);
|
||||
SetupUtils::copydir($sFolderPath, $sDestModulesDir.DIRECTORY_SEPARATOR.$sFolderName, $bUseSymLinks);
|
||||
}
|
||||
}
|
||||
|
||||
parent::CompileFrom($sSourceEnv, $bUseSymLinks);
|
||||
}
|
||||
|
||||
@@ -94,23 +102,43 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
*/
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
{
|
||||
\SetupLog::Info(__METHOD__);
|
||||
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
|
||||
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
|
||||
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
|
||||
$sDeltaName = basename($sDeltaFile);
|
||||
$sDeltaDir = dirname($sDeltaFile);
|
||||
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
|
||||
$aRet[$sDeltaId] = $oDelta;
|
||||
if ($this->bUseDelta) {
|
||||
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
|
||||
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
|
||||
$sDeltaName = basename($sDeltaFile);
|
||||
$sDeltaDir = dirname($sDeltaFile);
|
||||
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
|
||||
$aRet[$sDeltaId] = $oDelta;
|
||||
}
|
||||
}
|
||||
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
public function GetCustomDatamodelFiles()
|
||||
public function GetExtensionFoldersToAdd(): array
|
||||
{
|
||||
if (!is_null($this->aCustomDatamodelFiles)) {
|
||||
return $this->aCustomDatamodelFiles;
|
||||
if (is_null($this->aAdditionExtensionFoldersByCode)) {
|
||||
$this->InitViaItopCustomDatamodelTestCaseClasses();
|
||||
}
|
||||
|
||||
return $this->aAdditionExtensionFoldersByCode;
|
||||
}
|
||||
|
||||
public function GetCustomDatamodelFiles(): array
|
||||
{
|
||||
if (is_null($this->aCustomDatamodelFiles)) {
|
||||
$this->InitViaItopCustomDatamodelTestCaseClasses();
|
||||
}
|
||||
|
||||
return $this->aCustomDatamodelFiles;
|
||||
}
|
||||
|
||||
public function InitViaItopCustomDatamodelTestCaseClasses()
|
||||
{
|
||||
$this->aAdditionExtensionFoldersByCode = [];
|
||||
$this->aCustomDatamodelFiles = [];
|
||||
|
||||
// Search for the PHP files implementing the method GetDatamodelDeltaAbsPath
|
||||
@@ -169,16 +197,19 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
continue;
|
||||
}
|
||||
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
|
||||
if (!is_file($sDeltaFile)) {
|
||||
throw new \Exception("Unknown delta file: $sDeltaFile, from test class '$sClass'");
|
||||
}
|
||||
if (!in_array($sDeltaFile, $this->aCustomDatamodelFiles)) {
|
||||
$this->aCustomDatamodelFiles[] = $sDeltaFile;
|
||||
if (strlen($sDeltaFile) > 0) {
|
||||
if (!is_file($sDeltaFile)) {
|
||||
throw new \Exception("Unknown delta file: $sDeltaFile, from test class '$sClass'");
|
||||
}
|
||||
if (!in_array($sDeltaFile, $this->aCustomDatamodelFiles)) {
|
||||
$this->aCustomDatamodelFiles[] = $sDeltaFile;
|
||||
}
|
||||
}
|
||||
|
||||
$aExtensionsPaths = $oTestClassInstance->GetAdditionalFeaturePaths();
|
||||
$this->aAdditionExtensionFoldersByCode = array_merge($this->aAdditionExtensionFoldersByCode, $aExtensionsPaths);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aCustomDatamodelFiles;
|
||||
}
|
||||
|
||||
private function FindFilesModifiedAfter(float $fReferenceTimestamp, string $sPathToScan, array &$aModifiedFiles)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Application;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
class LoginTest extends ItopDataTestCase
|
||||
{
|
||||
protected $sConfigTmpBackupFile;
|
||||
protected $sConfigPath;
|
||||
protected $sLoginMode;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
clearstatcache();
|
||||
|
||||
// The test consists in requesting UI.php from outside iTop with a specific configuration
|
||||
// Hence the configuration file must be tweaked on disk (and restored)
|
||||
$this->sConfigPath = MetaModel::GetConfig()->GetLoadedFile();
|
||||
$this->sConfigTmpBackupFile = tempnam(sys_get_temp_dir(), "config_");
|
||||
file_put_contents($this->sConfigTmpBackupFile, file_get_contents($this->sConfigPath));
|
||||
|
||||
$oConfig = new \Config($this->sConfigPath);
|
||||
$this->sLoginMode = "unimplemented_loginmode";
|
||||
$oConfig->AddAllowedLoginTypes($this->sLoginMode);
|
||||
|
||||
@chmod($this->sConfigPath, 0770);
|
||||
$oConfig->WriteToFile();
|
||||
@chmod($this->sConfigPath, 0444);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (! is_null($this->sConfigTmpBackupFile) && is_file($this->sConfigTmpBackupFile)) {
|
||||
//put config back
|
||||
@chmod($this->sConfigPath, 0770);
|
||||
file_put_contents($this->sConfigPath, file_get_contents($this->sConfigTmpBackupFile));
|
||||
@chmod($this->sConfigPath, 0444);
|
||||
@unlink($this->sConfigTmpBackupFile);
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function CallItopUrlByCurl($sUri, ?array $aPostFields = [])
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
$sUrl = MetaModel::GetConfig()->Get('app_root_url')."/$sUri";
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
if (0 !== sizeof($aPostFields)) {
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$sOutput = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
}
|
||||
@@ -143,34 +143,12 @@ class QueryTest extends ItopDataTestCase
|
||||
{
|
||||
// compute request url
|
||||
$url = $oQuery->GetExportUrl();
|
||||
$aCurlOptions = [
|
||||
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
|
||||
CURLOPT_USERPWD => self::USER.':'.self::PASSWORD,
|
||||
];
|
||||
|
||||
// open curl
|
||||
$curl = curl_init();
|
||||
|
||||
// curl options
|
||||
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
||||
curl_setopt($curl, CURLOPT_USERPWD, self::USER.':'.self::PASSWORD);
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
// execute curl
|
||||
$result = curl_exec($curl);
|
||||
if (curl_errno($curl)) {
|
||||
$info = curl_getinfo($curl);
|
||||
var_export($info);
|
||||
var_dump([
|
||||
'url' => $url,
|
||||
'app_root_url:' => MetaModel::GetConfig()->Get('app_root_url'),
|
||||
'GetAbsoluteUrlAppRoot:' => \utils::GetAbsoluteUrlAppRoot(),
|
||||
]);
|
||||
}
|
||||
// close curl
|
||||
curl_close($curl);
|
||||
|
||||
return $result;
|
||||
return $this->CallUrl($url, [], $aCurlOptions);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
||||
@@ -7,30 +7,26 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
|
||||
$MySettings = [
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => 'http://%server(SERVER_NAME)?:localhost%/itop/iTop/',
|
||||
|
||||
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
);
|
||||
$MyModuleSettings = [
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
);
|
||||
?>
|
||||
$MyModules = [
|
||||
];
|
||||
|
||||
@@ -7,30 +7,26 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
|
||||
$MySettings = [
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => 'http://' . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost') . '/itop/iTop/',
|
||||
'app_root_url' => 'http://'.(isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost').'/itop/iTop/',
|
||||
|
||||
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
);
|
||||
$MyModuleSettings = [
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
);
|
||||
?>
|
||||
$MyModules = [
|
||||
];
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use AnalyzeInstallation;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use ModuleInstallationService;
|
||||
|
||||
class AnalyzeInstallationTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/AnalyzeInstallation.php');
|
||||
$this->RequireOnceItopFile('setup/ModuleInstallationService.php');
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/runtimeenv.class.inc.php');
|
||||
}
|
||||
|
||||
public static function AnalyzeInstallationProvider()
|
||||
{
|
||||
//$aModules = json_decode(file_get_contents(__DIR__.'/ressources/priv_modules.json'), true);
|
||||
$aAnalyzeInstallationOutput = json_decode(file_get_contents(__DIR__.'/ressources/analyze_installation_output.json'), true);
|
||||
$aAvailableModules = json_decode(file_get_contents(__DIR__.'/ressources/available_modules.json'), true);
|
||||
return [
|
||||
'new modules not in DB setup history' => [
|
||||
'aAvailableModules' => [
|
||||
'mandatory_module/1.0.0' => [
|
||||
"mandatory" => true,
|
||||
"ga" => "bu",
|
||||
],
|
||||
'optional_module/6.6.6' => [
|
||||
"mandatory" => false,
|
||||
"zo" => "meu",
|
||||
],
|
||||
],
|
||||
'aInstalledModules' => [],
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
'mandatory_module' => [
|
||||
"mandatory" => true,
|
||||
'installed_version' => '',
|
||||
'available_version' => '1.0.0',
|
||||
'install' => [
|
||||
'flag' => 2,
|
||||
'message' => 'the module is part of the application',
|
||||
],
|
||||
"ga" => "bu",
|
||||
],
|
||||
'optional_module' => [
|
||||
"mandatory" => false,
|
||||
'installed_version' => '',
|
||||
'available_version' => '6.6.6',
|
||||
"zo" => "meu",
|
||||
'install' => [
|
||||
'flag' => 1,
|
||||
'message' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'new modules ALREADY in DB setup history' => [
|
||||
'aAvailableModules' => [
|
||||
'mandatory_module/1.0.0' => [
|
||||
"mandatory" => true,
|
||||
"ga" => "bu",
|
||||
],
|
||||
'optional_module/6.6.6' => [
|
||||
"mandatory" => false,
|
||||
"zo" => "meu",
|
||||
],
|
||||
],
|
||||
'aInstalledModules' => json_decode(file_get_contents(__DIR__.'/ressources/priv_modules_simpleusecase.json'), true),
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '3.3.0-dev-svn',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
'mandatory_module' => [
|
||||
"mandatory" => true,
|
||||
'installed_version' => '3.3.0',
|
||||
'available_version' => '1.0.0',
|
||||
"ga" => "bu",
|
||||
'install' => [
|
||||
'flag' => 2,
|
||||
'message' => 'the module is part of the application',
|
||||
],
|
||||
'uninstall' => [
|
||||
'flag' => 3,
|
||||
'message' => 'the module is part of the application',
|
||||
],
|
||||
],
|
||||
'optional_module' => [
|
||||
"mandatory" => false,
|
||||
'installed_version' => '3.3.0',
|
||||
'available_version' => '6.6.6',
|
||||
"zo" => "meu",
|
||||
'install' => [
|
||||
'flag' => 1,
|
||||
'message' => '',
|
||||
],
|
||||
'uninstall' => [
|
||||
'flag' => 1,
|
||||
'message' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'dummyfirst installation' => [
|
||||
'aAvailableModules' => [],
|
||||
'aInstalledModules' => [],
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
],
|
||||
],
|
||||
'dummy 2nd installation' => [
|
||||
'aAvailableModules' => [],
|
||||
'aInstalledModules' => json_decode(file_get_contents(__DIR__.'/ressources/priv_modules2.json'), true),
|
||||
'expected' => [
|
||||
'_Root_' => [
|
||||
'installed_version' => '3.3.0-dev-svn',
|
||||
'available_version' => 'ITOP_VERSION_FULL',
|
||||
'name_code' => 'ITOP_APPLICATION',
|
||||
],
|
||||
],
|
||||
],
|
||||
'real_case' => [
|
||||
'aAvailableModules' => $aAvailableModules,
|
||||
'aInstalledModules' => json_decode(file_get_contents(__DIR__.'/ressources/priv_modules2.json'), true),
|
||||
'expected' => $aAnalyzeInstallationOutput,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider AnalyzeInstallationProvider
|
||||
*/
|
||||
public function testAnalyzeInstallation($aAvailableModules, $aInstalledModules, $expected)
|
||||
{
|
||||
$sContent = str_replace(['ITOP_VERSION_FULL', 'ITOP_APPLICATION'], [ITOP_VERSION_FULL, ITOP_APPLICATION], json_encode($expected));
|
||||
$expected = json_decode($sContent, true);
|
||||
|
||||
$this->SetNonPublicProperty(AnalyzeInstallation::GetInstance(), "aAvailableModules", $aAvailableModules);
|
||||
//$aModules = json_decode(file_get_contents(__DIR__.'/ressources/priv_modules2.json'), true);
|
||||
$this->SetNonPublicProperty(ModuleInstallationService::GetInstance(), "aSelectInstall", $aInstalledModules);
|
||||
|
||||
$oConfig = $this->createMock(\Config::class);
|
||||
|
||||
$modulesPath = [
|
||||
APPROOT.'extensions',
|
||||
];
|
||||
$aModules = AnalyzeInstallation::GetInstance()->AnalyzeInstallation($oConfig, $modulesPath, false, null);
|
||||
$this->assertEquals($expected, $aModules);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use ItopExtensionsMap;
|
||||
use ModuleDiscovery;
|
||||
|
||||
class ExtensionsMapTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp(); // TODO: Change the autogenerated stub
|
||||
$this->RequireOnceItopFile('/setup/unattended-install/InstallationFileService.php');
|
||||
ModuleDiscovery::ResetCache();
|
||||
|
||||
}
|
||||
|
||||
public function testGetAllExtensionsWithPreviouslyInstalledDoesNotCrash()
|
||||
{
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$aExtensions = $oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled();
|
||||
$this->assertGreaterThan(0, count($aExtensions));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use MissingDependencyException;
|
||||
use ModuleDiscovery;
|
||||
|
||||
class ModuleDiscoveryTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/runtimeenv.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_RealExample()
|
||||
{
|
||||
$aModules = json_decode(file_get_contents(__DIR__.'/ressources/reallife_discovered_modules.json'), true);
|
||||
|
||||
$aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true);
|
||||
|
||||
$aExpected = json_decode(file_get_contents(__DIR__.'/ressources/reallife_expected_ordered_modules.json'), true);
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_LoadOnlyChoosenModules()
|
||||
{
|
||||
$aChoices = ['id1', 'id2'];
|
||||
|
||||
$aModules = [
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, $aChoices);
|
||||
|
||||
$aExpected = [
|
||||
"id2/2",
|
||||
"id1/1",
|
||||
];
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_FailWhenChoosenModuleDependsOnUnchoosenModule()
|
||||
{
|
||||
$aChoices = ['id1'];
|
||||
|
||||
$aModules = [
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<TXT
|
||||
The following modules have unmet dependencies:
|
||||
label1 (id: id1/1) depends on: ❌ id2/2
|
||||
TXT;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDiscovery::OrderModulesByDependencies($aModules, true, $aChoices);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class WizStepModulesChoiceFake extends WizStepModulesChoice
|
||||
{
|
||||
public function __construct(WizardController $oWizard, $sCurrentState)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function setExtensionMap(iTopExtensionsMap $oMap)
|
||||
{
|
||||
$this->oExtensionsMap = $oMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use ItopExtensionsMap;
|
||||
use iTopExtensionsMapFake;
|
||||
use ModuleDiscovery;
|
||||
use WizardController;
|
||||
|
||||
class WizStepModulesChoiceTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('/setup/unattended-install/InstallationFileService.php');
|
||||
require_once __DIR__.'/iTopExtensionsMapFake.php';
|
||||
require_once __DIR__.'/WizStepModulesChoiceFake.php';
|
||||
|
||||
$this->oStep = new \WizStepModulesChoiceFake(new WizardController('', ''), '');
|
||||
ModuleDiscovery::ResetCache();
|
||||
}
|
||||
|
||||
public function ProviderComputeChoiceFlags()
|
||||
{
|
||||
return [
|
||||
'selected but not installed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => false,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => ['_0' => '_0'],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => false,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'not selected, not installed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => false,
|
||||
'checked' => false,
|
||||
],
|
||||
],
|
||||
'installed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => true,
|
||||
'disabled' => false,
|
||||
'checked' => false,
|
||||
],
|
||||
],
|
||||
'installed non uninstallable extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => false,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => false,
|
||||
'missing' => false,
|
||||
'installed' => true,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'mandatory extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => true,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'optional sub extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext1-1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
'sub_options' => [
|
||||
'options' => [
|
||||
[
|
||||
'extension_code' => 'itop-ext1-1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => false,
|
||||
'checked' => false,
|
||||
],
|
||||
],
|
||||
'mandatory sub extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext1-1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
'sub_options' => [
|
||||
'options' => [
|
||||
[
|
||||
'extension_code' => 'itop-ext1-1',
|
||||
'mandatory' => true,
|
||||
'uninstallable' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => false,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
'non uninstallable sub extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
'itop-ext1-1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'bUpgrade' => true,
|
||||
'bDisableUninstallCheck' => false,
|
||||
'sChoiceId' => '_0',
|
||||
'aStepInfo' => [
|
||||
'extension_code' => 'itop-ext1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => true,
|
||||
'sub_options' => [
|
||||
'options' => [
|
||||
[
|
||||
'extension_code' => 'itop-ext1-1',
|
||||
'mandatory' => false,
|
||||
'uninstallable' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'aExpectedFlags' => [
|
||||
'uninstallable' => true,
|
||||
'missing' => false,
|
||||
'installed' => true,
|
||||
'disabled' => true,
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ProviderComputeChoiceFlags
|
||||
*/
|
||||
public function testComputeChoiceFlags($aExtensions, $bUpgrade, $bDisableUninstallCheck, $sChoiceId, $aStepInfo, $aSelected, $aExpectedFlags)
|
||||
{
|
||||
$this->oStep->setExtensionMap(iTopExtensionsMapFake::createFromArray($aExtensions));
|
||||
$aFlags = $this->oStep->ComputeChoiceFlags($aStepInfo, $sChoiceId, $aSelected, false, $bDisableUninstallCheck, $bUpgrade);
|
||||
$this->assertEquals($aExpectedFlags, $aFlags);
|
||||
}
|
||||
|
||||
public function ProviderGetAddedAndRemovedExtensions()
|
||||
{
|
||||
return [
|
||||
'no extensions' => [
|
||||
'aExtensions' => [],
|
||||
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'no extensions selected' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'no extensions removed' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'aSelected' => ['itop-ext1'],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'One added extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
],
|
||||
'aSelected' => ['itop-ext1'],
|
||||
'sExpectedAddedList' => '<ul><li>itop-ext1</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>No extension removed.</li></ul>',
|
||||
],
|
||||
'One removed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>itop-ext1</li></ul>',
|
||||
],
|
||||
'Forced removed extension' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext1' => [
|
||||
'installed' => true,
|
||||
'uninstallable' => false,
|
||||
],
|
||||
],
|
||||
'aSelected' => [],
|
||||
'sExpectedAddedList' => '<ul><li>No extension added.</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>itop-ext1 (forced uninstallation)</li></ul>',
|
||||
],
|
||||
'added and removed extensions' => [
|
||||
'aExtensions' => [
|
||||
'itop-ext-added1' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext-added2' => [
|
||||
'installed' => false,
|
||||
],
|
||||
'itop-ext-removed1' => [
|
||||
'installed' => true,
|
||||
],
|
||||
'itop-ext-removed2' => [
|
||||
'installed' => true,
|
||||
],
|
||||
],
|
||||
'aSelected' => ['itop-ext-added1', 'itop-ext-added2'],
|
||||
'sExpectedAddedList' => '<ul><li>itop-ext-added1</li><li>itop-ext-added2</li></ul>',
|
||||
'sExpectedRemovedList' => '<ul><li>itop-ext-removed1</li><li>itop-ext-removed2</li></ul>',
|
||||
],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ProviderGetAddedAndRemovedExtensions
|
||||
*/
|
||||
public function testGetAddedAndRemovedExtensions($aExtensions, $aSelectedExtensions, $sExpectedAddedList, $sExpectedRemovedList)
|
||||
{
|
||||
$this->oStep->setExtensionMap(iTopExtensionsMapFake::createFromArray($aExtensions));
|
||||
[$sAddedList, $sRemovedList] = $this->oStep->GetAddedAndRemovedExtensions($aSelectedExtensions);
|
||||
$this->assertEquals($sExpectedAddedList, $sAddedList);
|
||||
$this->assertEquals($sExpectedRemovedList, $sRemovedList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup\FeatureRemoval;
|
||||
|
||||
use Combodo\iTop\Setup\FeatureRemoval\ModelReflectionSerializer;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
class ModelSerializationTest extends ItopDataTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('/setup/feature_removal/ModelReflectionSerializer.php');
|
||||
}
|
||||
|
||||
public function testGetModelFromEnvironment()
|
||||
{
|
||||
$aModel = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->GetTestEnvironment());
|
||||
$this->assertEqualsCanonicalizing(MetaModel::GetClasses(), $aModel);
|
||||
}
|
||||
|
||||
public function testGetModelFromEnvironmentFailure()
|
||||
{
|
||||
$this->expectException(\CoreException::class);
|
||||
$this->expectExceptionMessage("Cannot get classes");
|
||||
ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment('gabuzomeu');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup\FeatureRemoval;
|
||||
|
||||
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
|
||||
use Exception;
|
||||
|
||||
class SetupAuditTest extends ItopCustomDatamodelTestCase
|
||||
{
|
||||
public const ENVT = 'php-unit-extensionremoval-tests';
|
||||
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
//no delta: empty path provided
|
||||
return "";
|
||||
}
|
||||
|
||||
public function GetAdditionalFeaturePaths(): array
|
||||
{
|
||||
$aFeaturePaths = [];
|
||||
foreach (glob(__DIR__."/additional_features/*", GLOB_ONLYDIR) as $aFeaturePath) {
|
||||
$sCode = basename($aFeaturePath);
|
||||
$aFeaturePaths[$sCode] = $aFeaturePath;
|
||||
}
|
||||
|
||||
return $aFeaturePaths;
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::LoadRequiredItopFiles();
|
||||
$this->oEnvironment = new UnitTestRunTimeEnvironment(self::ENVT);
|
||||
$this->oEnvironment->bUseDelta = false;
|
||||
$this->oEnvironment->bUseAdditionalFeatures = true;
|
||||
parent::setUp();
|
||||
|
||||
$this->RequireOnceItopFile('/setup/feature_removal/SetupAudit.php');
|
||||
$this->RequireOnceItopFile('/setup/feature_removal/DryRemovalRuntimeEnvironment.php');
|
||||
}
|
||||
|
||||
public function GetTestEnvironment(): string
|
||||
{
|
||||
return self::ENVT;
|
||||
}
|
||||
|
||||
public function testComputeDryRemoval()
|
||||
{
|
||||
$oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment();
|
||||
$oDryRemovalRuntimeEnvt->Prepare($this->GetTestEnvironment(), ['nominal_ext1', 'finalclass_ext2']);
|
||||
$oDryRemovalRuntimeEnvt->CompileFrom($this->GetTestEnvironment());
|
||||
|
||||
$oSetupAudit = new SetupAudit(\MetaModel::GetEnvironment());
|
||||
|
||||
$expected = [
|
||||
"Feature1Module1MyClass",
|
||||
"FinalClassFeature2Module1MyClass",
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation",
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetRemovedClasses());
|
||||
|
||||
$expected = [
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation" => 0,
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetIssues());
|
||||
}
|
||||
|
||||
public function testGetIssues()
|
||||
{
|
||||
$sUID = "AuditExtensionsCleanupRules_".uniqid();
|
||||
$oOrg = $this->CreateOrganization($sUID);
|
||||
$this->createObject('FinalClassFeature1Module1MyFinalClassFromLocation', ['org_id' => $oOrg->GetKey(), 'name' => $sUID, 'name2' => uniqid()]);
|
||||
|
||||
$oSetupAudit = new SetupAudit(\MetaModel::GetEnvironment());
|
||||
$aRemovedClasses = [
|
||||
"Feature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyFinalClassFromLocation",
|
||||
"FinalClassFeature2Module1MyClass",
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation",
|
||||
];
|
||||
|
||||
//avoid setup dry computation
|
||||
$this->SetNonPublicProperty($oSetupAudit, 'aRemovedClasses', $aRemovedClasses);
|
||||
|
||||
$expected = [
|
||||
"FinalClassFeature1Module1MyFinalClassFromLocation" => 1,
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation" => 0,
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetIssues());
|
||||
}
|
||||
|
||||
public function testAuditExtensionsCleanupRulesFailASAP()
|
||||
{
|
||||
$sUID = "AuditExtensionsCleanupRules_".uniqid();
|
||||
$oOrg = $this->CreateOrganization($sUID);
|
||||
$this->createObject('FinalClassFeature1Module1MyFinalClassFromLocation', ['org_id' => $oOrg->GetKey(), 'name' => $sUID, 'name2' => uniqid()]);
|
||||
$this->createObject('FinalClassFeature2Module1MyFinalClassFromLocation', ['org_id' => $oOrg->GetKey(), 'name' => $sUID, 'name2' => uniqid()]);
|
||||
|
||||
$oSetupAudit = new SetupAudit(\MetaModel::GetEnvironment());
|
||||
$aRemovedClasses = [
|
||||
"Feature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyClass",
|
||||
"FinalClassFeature1Module1MyFinalClassFromLocation",
|
||||
"FinalClassFeature2Module1MyClass",
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation",
|
||||
];
|
||||
|
||||
//avoid setup dry computation
|
||||
$this->SetNonPublicProperty($oSetupAudit, 'aRemovedClasses', $aRemovedClasses);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage('FinalClassFeature1Module1MyFinalClassFromLocation');
|
||||
$oSetupAudit->GetIssues(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension format="1.0">
|
||||
<extension_code>finalclass_ext1</extension_code>
|
||||
<company>Combodo SARL</company>
|
||||
<author><![CDATA[Odain]]></author>
|
||||
<label><![CDATA[Ext For Test]]></label>
|
||||
<description><![CDATA[Ext For Test]]></description>
|
||||
<version>6.6.6</version>
|
||||
<modules type="array">
|
||||
<module>
|
||||
<id>finalclass_ext1_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>
|
||||
@@ -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="FinalClassFeature1Module1MyFinalClassFromLocation" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature1Module1MyFinalClassFromLocation</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="FinalClassFeature1Module1MyClass" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature1Module1MyClass</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>
|
||||
@@ -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
|
||||
@@ -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_ext1_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_ext1_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
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension format="1.0">
|
||||
<extension_code>finalclass_ext2</extension_code>
|
||||
<company>Combodo SARL</company>
|
||||
<author><![CDATA[Odain]]></author>
|
||||
<label><![CDATA[Ext For Test]]></label>
|
||||
<description><![CDATA[Ext For Test]]></description>
|
||||
<version>6.6.6</version>
|
||||
<modules type="array">
|
||||
<module>
|
||||
<id>finalclass_ext2_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>
|
||||
@@ -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="FinalClassFeature2Module1MyFinalClassFromLocation" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature2Module1MyFinalClassFromLocation</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="FinalClassFeature2Module1MyClass" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature2Module1MyClass</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>
|
||||
@@ -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
|
||||
@@ -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_ext2_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_ext2_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
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension format="1.0">
|
||||
<extension_code>nominal_ext1</extension_code>
|
||||
<company>Combodo SARL</company>
|
||||
<author><![CDATA[Odain]]></author>
|
||||
<label><![CDATA[Ext For Test]]></label>
|
||||
<description><![CDATA[Ext For Test]]></description>
|
||||
<version>6.6.6</version>
|
||||
<modules type="array">
|
||||
<module>
|
||||
<id>nominal_ext1_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>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
|
||||
<classes>
|
||||
<class id="Feature1Module1MyClass" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>Feature1Module1MyClass</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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
'nominal_ext1_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.nominal_ext1_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
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
class iTopExtensionsMapFake extends iTopExtensionsMap
|
||||
{
|
||||
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
|
||||
{
|
||||
$this->aExtensions = [];
|
||||
$this->aExtensionsByCode = [];
|
||||
$this->aScannedDirs = [];
|
||||
}
|
||||
|
||||
public static function createFromArray($aExtensions)
|
||||
{
|
||||
$oMap = new static();
|
||||
|
||||
foreach ($aExtensions as $sCode => $aExtension) {
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->sCode = $sCode;
|
||||
$oExtension->sLabel = $sCode;
|
||||
$oExtension->bInstalled = $aExtension['installed'];
|
||||
$oExtension->aModules = $aExtension['modules'] ?? [];
|
||||
$oExtension->bCanBeUninstalled = $aExtension['uninstallable'] ?? null;
|
||||
$oExtension->sVersion = $aExtension['version'] ?? '1.0.0';
|
||||
$oExtension->aModuleInfo = [];
|
||||
$oMap->AddExtension($oExtension);
|
||||
}
|
||||
return $oMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\DependencyExpression;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class DependencyExpressionTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/moduledependency/dependencyexpression.class.inc.php');
|
||||
}
|
||||
|
||||
public function testModuleDependencyInit_Invalid()
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression('||');
|
||||
$this->assertFalse($oModuleDependency->IsValid());
|
||||
$this->assertFalse($oModuleDependency->IsResolved());
|
||||
}
|
||||
|
||||
public static function WithOperatorProvider()
|
||||
{
|
||||
return [
|
||||
"nominal case" => [
|
||||
"dep" => "itop-config-mgmt/2.4.0",
|
||||
'expected_operator' => '>=',
|
||||
],
|
||||
">" => [
|
||||
"dep" => "itop-config-mgmt/>2.4.0",
|
||||
'expected_operator' => '>',
|
||||
],
|
||||
">=" => [
|
||||
"dep" => "itop-config-mgmt/>=2.4.0",
|
||||
'expected_operator' => '>=',
|
||||
],
|
||||
"<" => [
|
||||
"dep" => "itop-config-mgmt/<2.4.0",
|
||||
'expected_operator' => '<',
|
||||
],
|
||||
"<=" => [
|
||||
"dep" => "itop-config-mgmt/<=2.4.0",
|
||||
'expected_operator' => '<=',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithOperatorProvider
|
||||
*/
|
||||
public function testModuleDependencyInit_WithOperator($sDepId, $sExpectedOperator)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sDepId);
|
||||
$this->assertEquals([$sDepId => ['itop-config-mgmt', $sExpectedOperator, '2.4.0']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertTrue($oModuleDependency->IsValid());
|
||||
$this->assertFalse($oModuleDependency->IsResolved());
|
||||
;
|
||||
$this->assertEquals(['itop-config-mgmt'], $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
|
||||
public static function WithVariousOperatorProvider()
|
||||
{
|
||||
$aInternalStructure = ['itop-structure/3.0.0' => ['itop-structure', ">=", '3.0.0'], 'itop-portal/<3.2.1' => ['itop-portal', "<", '3.2.1']];
|
||||
|
||||
return [
|
||||
'&&' => [
|
||||
'sDepId' => 'itop-structure/3.0.0 && itop-portal/<3.2.1',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
'&& with parenthesis' => [
|
||||
'sDepId' => '(itop-structure/3.0.0) && (itop-portal/<3.2.1)',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
'||' => [
|
||||
'sDepId' => 'itop-structure/3.0.0 || itop-portal/<3.2.1',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
'|| with parenthesis' => [
|
||||
'sDepId' => '(itop-structure/3.0.0) || (itop-portal/<3.2.1)',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithVariousOperatorProvider
|
||||
*/
|
||||
public function testModuleDependencyInit_WithOperand($sDepId, $sExpected)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sDepId);
|
||||
$this->assertEquals($sExpected, $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertTrue($oModuleDependency->IsValid());
|
||||
;
|
||||
$this->assertEquals(['itop-structure', 'itop-portal'], $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
|
||||
public static function SimpleDependencyExpressionIsResolvedProvider()
|
||||
{
|
||||
return [
|
||||
'unresolved with major version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '1.2.3'],
|
||||
'expected_is_resolved' => false,
|
||||
],
|
||||
'unresolved with minor version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.1',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '2.4.0-1'],
|
||||
'expected_is_resolved' => false,
|
||||
],
|
||||
'resolution OK with major version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '2.4.2'],
|
||||
'expected_is_resolved' => true,
|
||||
],
|
||||
'resolution OK with minor version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '2.4.0-1'],
|
||||
'expected_is_resolved' => true,
|
||||
],
|
||||
'unproper use of api' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => [],
|
||||
'expected_is_resolved' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider SimpleDependencyExpressionIsResolvedProvider
|
||||
*/
|
||||
public function testSimpleDependencyExpressionIsResolved($sExpression, $aModuleVersions, $bExpectedResolved)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sExpression);
|
||||
$oModuleDependency->UpdateModuleResolutionState($aModuleVersions, ['itop-config-mgmt' => true]);
|
||||
$this->assertEquals($bExpectedResolved, $oModuleDependency->IsResolved());
|
||||
if ($bExpectedResolved) {
|
||||
$this->assertEquals([], $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
}
|
||||
|
||||
public static function ComplexDependencyExpressionIsResolvedProvider()
|
||||
{
|
||||
$aAllModules = ['itop-structure' => true, 'itop-portal' => true];
|
||||
|
||||
return [
|
||||
'and + unresolved due to missing itop-portal' => [
|
||||
'expr' => 'itop-structure/3.0.0 && itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-portal'],
|
||||
],
|
||||
'and + unresolved due to unsifficient itop-portal version' => [
|
||||
'expr' => 'itop-structure/3.0.0 && itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0', 'itop-portal' => '1.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-portal'],
|
||||
],
|
||||
'and + resolved' => [
|
||||
'expr' => 'itop-structure/3.0.0 && itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0', 'itop-portal' => '3.3.3'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => [],
|
||||
],
|
||||
'or||true (step1) + dependency expression evaluation is delayed for sorting purpose' => [
|
||||
'expr' => 'itop-structure/3.0.0||true',
|
||||
'resolved_module_versions' => [],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-structure'],
|
||||
],
|
||||
'or||true (step2) + expression is evaluated because itop-structure has been resolved' => [
|
||||
'expr' => 'itop-structure/3.0.0||true',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => [],
|
||||
],
|
||||
'or||true + resolved DIRECTLY as itop-structure is not on disk (all_modules)' => [
|
||||
'expr' => 'itop-structure/3.0.0||true',
|
||||
'resolved_module_versions' => [],
|
||||
'all_modules' => [],
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => ['itop-structure'],
|
||||
],
|
||||
'or + unresolved because dependency trick used to sort as well' => [
|
||||
'expr' => 'itop-structure/3.0.0 || itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-portal'],
|
||||
],
|
||||
'1 can be used as a boolean' => [
|
||||
'expr' => 'true||1',
|
||||
'resolved_module_versions' => [],
|
||||
'all_modules' => [],
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ComplexDependencyExpressionIsResolvedProvider
|
||||
*/
|
||||
public function testComplexDependencyExpressionIsResolved($sExpression, $aModuleVersions, $aAllModules, $bExpectedResolved, $aRemainingModuleNames)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sExpression);
|
||||
|
||||
$oModuleDependency->UpdateModuleResolutionState($aModuleVersions, $aAllModules);
|
||||
$this->assertEquals($aRemainingModuleNames, $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
$this->assertEquals($bExpectedResolved, $oModuleDependency->IsResolved());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\Setup\ModuleDependency;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use MissingDependencyException;
|
||||
|
||||
class ModuleDependencySortTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/moduledependency/moduledependencysort.class.inc.php');
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_CheckExceptionWhenAllModuleUnresolved()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/123" => [
|
||||
'dependencies' => [ 'id3/666', 'id4/666'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/456" => [
|
||||
'dependencies' => ['id3/666'],
|
||||
'label' => 'label2',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<MSG
|
||||
The following modules have unmet dependencies:
|
||||
label2 (id: id2/456) depends on: ❌ id3/666,
|
||||
label1 (id: id1/123) depends on: ❌ id3/666 + ❌ id4/666
|
||||
MSG;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_CheckExceptionWhenSomeModuleUnresolved()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/123" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666', 'id3/789'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/456" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/789" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<MSG
|
||||
The following modules have unmet dependencies:
|
||||
label3 (id: id3/789) depends on: ❌ id4/666,
|
||||
label1 (id: id1/123) depends on: ❌ id4/666 + ❌ id3/789
|
||||
MSG;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_CheckExceptionWhenCircularDependencies()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => ['id3/3'],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ['id4/4'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
"id4/4" => [
|
||||
'dependencies' => ['id1/1'],
|
||||
'label' => 'label4',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<MSG
|
||||
The following modules have unmet dependencies:
|
||||
label1 (id: id1/1) depends on: ❌ id2/2,
|
||||
label4 (id: id4/4) depends on: ❌ id1/1,
|
||||
label3 (id: id3/3) depends on: ❌ id4/4,
|
||||
label2 (id: id2/2) depends on: ❌ id3/3
|
||||
MSG;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_KeepGoingEvenWithFailure()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/123" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666', 'id3/789'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/456" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/789" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, false);
|
||||
|
||||
$aExpected = [
|
||||
'id2/456',
|
||||
];
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_Nominalcase()
|
||||
{
|
||||
$aModules = [
|
||||
"id0/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => ['id3/3'],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ['id4/4'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
"id4/4" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label4',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id4/4",
|
||||
"id3/3",
|
||||
"id2/2",
|
||||
"id0/1",
|
||||
"id1/1",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
//warning : tricky usecase
|
||||
public function testOrderModulesByDependencies_AllTermsOfOrExpressionWillImpactTheOrder()
|
||||
{
|
||||
$aModules = [
|
||||
"id0/1" => [
|
||||
'dependencies' => [ 'id2/2 || id1/1'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id2/2",
|
||||
"id1/1",
|
||||
"id0/1",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
//WARNING: alphabetical order make setup are determinititic
|
||||
public function testOrderModulesByDependencies_ResolveNoDependendenciesOrderByAlphabeticalOrder()
|
||||
{
|
||||
$aModules = [
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label3',
|
||||
],
|
||||
"id4/4" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label4',
|
||||
],
|
||||
"id0/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label0',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id0/1",
|
||||
"id1/1",
|
||||
"id2/2",
|
||||
"id3/3",
|
||||
"id4/4",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_AlphabeticalOrderWithDependencies()
|
||||
{
|
||||
$aModules = [
|
||||
"id2/2" => [
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id1/1",
|
||||
"id2/2",
|
||||
"id3/3",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_AlphabeticalOrderWithDependencies2()
|
||||
{
|
||||
$aModules = [
|
||||
"z_id2/2" => [ //difference here
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id1/1",
|
||||
"id3/3",
|
||||
"z_id2/2",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_NoDependencies()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'c', []);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'b', []);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'a', []);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(['a', 'b', 'c'], array_keys($aUnresolvedDependencyModules));
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_NominalUseCase()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-structure/2.7.1', []);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'itop-structure/2.7.1',
|
||||
'itop-config-mgmt/123',
|
||||
'itop-tickets/2.0.0',
|
||||
'itop-change-mgmt/456',
|
||||
],
|
||||
array_keys($aUnresolvedDependencyModules)
|
||||
);
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_NominalUseCaseWithMissingDependency()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'itop-config-mgmt/123',
|
||||
'itop-tickets/2.0.0',
|
||||
'itop-change-mgmt/456',
|
||||
],
|
||||
array_keys($aUnresolvedDependencyModules)
|
||||
);
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_FurtherVersionsOfSameModule()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleA/1', []);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleA/2', ['moduleC/1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleB/1', ['moduleA/1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleC/1', []);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'moduleA/1',
|
||||
'moduleC/1',
|
||||
'moduleA/2',
|
||||
'moduleB/1',
|
||||
],
|
||||
array_keys($aUnresolvedDependencyModules)
|
||||
);
|
||||
}
|
||||
|
||||
private function AddModule(array &$aUnresolvedDependencyModules, string $sModuleId, array $aDeps)
|
||||
{
|
||||
$oModule = new Module($sModuleId);
|
||||
$oModule->SetDependencies($aDeps);
|
||||
$aUnresolvedDependencyModules[$sModuleId] = $oModule;
|
||||
}
|
||||
|
||||
private function SortModulesByCountOfDepencenciesDescending(array &$aUnresolvedDependencyModules)
|
||||
{
|
||||
$this->InvokeNonPublicMethod(ModuleDependencySort::class, 'SortModulesByCountOfDepencenciesDescending', ModuleDependencySort::GetInstance(), [&$aUnresolvedDependencyModules]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class ModuleTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/moduledependency/module.class.inc.php');
|
||||
}
|
||||
|
||||
public function testModuleInit()
|
||||
{
|
||||
$oModule = new Module("itop-config-mgmt/2.4.0");
|
||||
$this->assertEquals("itop-config-mgmt", $oModule->GetModuleName());
|
||||
$this->assertEquals("2.4.0", $oModule->GetVersion());
|
||||
$this->assertEquals("itop-config-mgmt/2.4.0", $oModule->GetModuleId());
|
||||
}
|
||||
|
||||
public function testModuleInit_NoVersion()
|
||||
{
|
||||
$oModule = new Module("itop-config-mgmt");
|
||||
$this->assertEquals("itop-config-mgmt", $oModule->GetModuleName());
|
||||
$this->assertEquals("1.0.0", $oModule->GetVersion());
|
||||
$this->assertEquals("itop-config-mgmt", $oModule->GetModuleId());
|
||||
}
|
||||
|
||||
public function testSetDependencies_ComplexExpressionsParsing()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-datacenter-mgmt-services");
|
||||
$oModule->SetDependencies([
|
||||
'itop-config-mgmt/>2.7.1',
|
||||
'itop-service-mgmt/=2.7.1 || itop-service-mgmt-provider/<=2.7.1',
|
||||
'itop-datacenter-mgmt/3.1.0 || true && false',
|
||||
]);
|
||||
$this->assertEquals(
|
||||
['itop-config-mgmt', 'itop-service-mgmt', 'itop-service-mgmt-provider', 'itop-datacenter-mgmt' ],
|
||||
$oModule->GetUnresolvedDependencyModuleNames()
|
||||
);
|
||||
}
|
||||
|
||||
public function testIsResolved_Unresolved()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-cmdb-ticket");
|
||||
$oModule->SetDependencies(['itop-config-mgmt/2.7.1', 'itop-tickets/2.7.0']);
|
||||
$this->assertEquals(['itop-config-mgmt', 'itop-tickets'], $oModule->GetUnresolvedDependencyModuleNames(), "all dependencies are unresolved");
|
||||
$this->assertFalse($oModule->IsResolved());
|
||||
|
||||
$oModule->UpdateModuleResolutionState([], []);
|
||||
$this->assertFalse($oModule->IsResolved(), "all dependencies are still unresolved");
|
||||
}
|
||||
|
||||
public function testIsResolved_PartialResolution()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-cmdb-ticket");
|
||||
$oModule->SetDependencies(['itop-config-mgmt/2.7.1', 'itop-tickets/2.7.0']);
|
||||
|
||||
$oModule->UpdateModuleResolutionState(['itop-config-mgmt' => '2.7.1'], ['itop-config-mgmt' => true]);
|
||||
$this->assertFalse($oModule->IsResolved(), "some dependencies are still unresolved");
|
||||
$this->assertEquals(['itop-tickets'], $oModule->GetUnresolvedDependencyModuleNames(), 'one dependency is remaining');
|
||||
}
|
||||
|
||||
public function testIsResolved_OK()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-cmdb-ticket");
|
||||
$oModule->SetDependencies(['itop-config-mgmt/2.7.1', 'itop-tickets/2.7.0']);
|
||||
|
||||
$oModule->UpdateModuleResolutionState(['itop-config-mgmt' => '2.7.1', 'itop-tickets' => '2.7.0'], ['itop-config-mgmt' => true, 'itop-tickets' => true]);
|
||||
$this->assertTrue($oModule->IsResolved());
|
||||
$this->assertEquals([], $oModule->GetUnresolvedDependencyModuleNames());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,642 @@
|
||||
[
|
||||
{
|
||||
"0": "330",
|
||||
"id": "330",
|
||||
"1": "iTop",
|
||||
"name": "iTop",
|
||||
"2": "3.3.0-dev-svn",
|
||||
"version": "3.3.0-dev-svn",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"comment": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"5": "0",
|
||||
"parent_id": "0",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "331",
|
||||
"id": "331",
|
||||
"1": "authent-cas",
|
||||
"name": "authent-cas",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "332",
|
||||
"id": "332",
|
||||
"1": "authent-external",
|
||||
"name": "authent-external",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "333",
|
||||
"id": "333",
|
||||
"1": "authent-ldap",
|
||||
"name": "authent-ldap",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "334",
|
||||
"id": "334",
|
||||
"1": "authent-local",
|
||||
"name": "authent-local",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "335",
|
||||
"id": "335",
|
||||
"1": "combodo-backoffice-darkmoon-theme",
|
||||
"name": "combodo-backoffice-darkmoon-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "336",
|
||||
"id": "336",
|
||||
"1": "combodo-backoffice-fullmoon-high-contrast-theme",
|
||||
"name": "combodo-backoffice-fullmoon-high-contrast-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "337",
|
||||
"id": "337",
|
||||
"1": "combodo-backoffice-fullmoon-protanopia-deuteranopia-theme",
|
||||
"name": "combodo-backoffice-fullmoon-protanopia-deuteranopia-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "338",
|
||||
"id": "338",
|
||||
"1": "combodo-backoffice-fullmoon-tritanopia-theme",
|
||||
"name": "combodo-backoffice-fullmoon-tritanopia-theme",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "339",
|
||||
"id": "339",
|
||||
"1": "itop-backup",
|
||||
"name": "itop-backup",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "340",
|
||||
"id": "340",
|
||||
"1": "itop-config",
|
||||
"name": "itop-config",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "341",
|
||||
"id": "341",
|
||||
"1": "itop-files-information",
|
||||
"name": "itop-files-information",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "342",
|
||||
"id": "342",
|
||||
"1": "itop-portal-base",
|
||||
"name": "itop-portal-base",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "343",
|
||||
"id": "343",
|
||||
"1": "itop-profiles-itil",
|
||||
"name": "itop-profiles-itil",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "344",
|
||||
"id": "344",
|
||||
"1": "itop-sla-computation",
|
||||
"name": "itop-sla-computation",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "345",
|
||||
"id": "345",
|
||||
"1": "itop-structure",
|
||||
"name": "itop-structure",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "346",
|
||||
"id": "346",
|
||||
"1": "itop-welcome-itil",
|
||||
"name": "itop-welcome-itil",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"comment": "Done by the setup program\nMandatory\nHidden (selected automatically)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "347",
|
||||
"id": "347",
|
||||
"1": "itop-config-mgmt",
|
||||
"name": "itop-config-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "348",
|
||||
"id": "348",
|
||||
"1": "itop-attachments",
|
||||
"name": "itop-attachments",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "349",
|
||||
"id": "349",
|
||||
"1": "itop-tickets",
|
||||
"name": "itop-tickets",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/2.7.1",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "350",
|
||||
"id": "350",
|
||||
"1": "combodo-db-tools",
|
||||
"name": "combodo-db-tools",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.0.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.0.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "351",
|
||||
"id": "351",
|
||||
"1": "itop-core-update",
|
||||
"name": "itop-core-update",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-files-information\/2.7.0\nDepends on module: combodo-db-tools\/2.7.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-files-information\/2.7.0\nDepends on module: combodo-db-tools\/2.7.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "352",
|
||||
"id": "352",
|
||||
"1": "itop-hub-connector",
|
||||
"name": "itop-hub-connector",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "353",
|
||||
"id": "353",
|
||||
"1": "itop-oauth-client",
|
||||
"name": "itop-oauth-client",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-welcome-itil\/3.1.0,",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-welcome-itil\/3.1.0,",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "354",
|
||||
"id": "354",
|
||||
"1": "itop-themes-compat",
|
||||
"name": "itop-themes-compat",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-structure\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "355",
|
||||
"id": "355",
|
||||
"1": "itop-datacenter-mgmt",
|
||||
"name": "itop-datacenter-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "356",
|
||||
"id": "356",
|
||||
"1": "itop-endusers-devices",
|
||||
"name": "itop-endusers-devices",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "357",
|
||||
"id": "357",
|
||||
"1": "itop-storage-mgmt",
|
||||
"name": "itop-storage-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "358",
|
||||
"id": "358",
|
||||
"1": "itop-virtualization-mgmt",
|
||||
"name": "itop-virtualization-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "359",
|
||||
"id": "359",
|
||||
"1": "itop-bridge-cmdb-ticket",
|
||||
"name": "itop-bridge-cmdb-ticket",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-tickets\/2.7.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-tickets\/2.7.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "360",
|
||||
"id": "360",
|
||||
"1": "itop-bridge-virtualization-storage",
|
||||
"name": "itop-bridge-virtualization-storage",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-storage-mgmt\/2.2.0\nDepends on module: itop-virtualization-mgmt\/2.2.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-storage-mgmt\/2.2.0\nDepends on module: itop-virtualization-mgmt\/2.2.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "361",
|
||||
"id": "361",
|
||||
"1": "itop-service-mgmt",
|
||||
"name": "itop-service-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.0.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.0.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "362",
|
||||
"id": "362",
|
||||
"1": "itop-bridge-cmdb-services",
|
||||
"name": "itop-bridge-cmdb-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "363",
|
||||
"id": "363",
|
||||
"1": "itop-bridge-datacenter-mgmt-services",
|
||||
"name": "itop-bridge-datacenter-mgmt-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-datacenter-mgmt\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-datacenter-mgmt\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "364",
|
||||
"id": "364",
|
||||
"1": "itop-bridge-endusers-devices-services",
|
||||
"name": "itop-bridge-endusers-devices-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-endusers-devices\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-endusers-devices\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "365",
|
||||
"id": "365",
|
||||
"1": "itop-bridge-storage-mgmt-services",
|
||||
"name": "itop-bridge-storage-mgmt-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-storage-mgmt\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-storage-mgmt\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "366",
|
||||
"id": "366",
|
||||
"1": "itop-bridge-virtualization-mgmt-services",
|
||||
"name": "itop-bridge-virtualization-mgmt-services",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-virtualization-mgmt\/3.1.0",
|
||||
"comment": "Done by the setup program\nOptional\nHidden (selected automatically)\nDepends on module: itop-config-mgmt\/2.7.1\nDepends on module: itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1\nDepends on module: itop-virtualization-mgmt\/3.1.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "367",
|
||||
"id": "367",
|
||||
"1": "itop-request-mgmt",
|
||||
"name": "itop-request-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.4.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-tickets\/2.4.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "368",
|
||||
"id": "368",
|
||||
"1": "itop-portal",
|
||||
"name": "itop-portal",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-portal-base\/2.7.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-portal-base\/2.7.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "369",
|
||||
"id": "369",
|
||||
"1": "itop-change-mgmt",
|
||||
"name": "itop-change-mgmt",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0\nDepends on module: itop-tickets\/2.0.0",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)\nDepends on module: itop-config-mgmt\/2.2.0\nDepends on module: itop-tickets\/2.0.0",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"0": "330",
|
||||
"id": "330",
|
||||
"1": "iTop",
|
||||
"name": "iTop",
|
||||
"2": "3.3.0-dev-svn",
|
||||
"version": "3.3.0-dev-svn",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"comment": "Done by the setup program\nBuilt on $WCNOW$",
|
||||
"5": "0",
|
||||
"parent_id": "0",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "331",
|
||||
"id": "331",
|
||||
"1": "mandatory_module",
|
||||
"name": "mandatory_module",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nMandatory\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "yes"
|
||||
},
|
||||
{
|
||||
"0": "332",
|
||||
"id": "332",
|
||||
"1": "optional_module",
|
||||
"name": "optional_module",
|
||||
"2": "3.3.0",
|
||||
"version": "3.3.0",
|
||||
"3": "2025-11-10 11:50:12",
|
||||
"installed": "2025-11-10 11:50:12",
|
||||
"4": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"comment": "Done by the setup program\nOptional\nVisible (during the setup)",
|
||||
"5": "330",
|
||||
"parent_id": "330",
|
||||
"6": "yes",
|
||||
"uninstallable": "no"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,267 @@
|
||||
{
|
||||
"authent-cas\/3.2.1": {
|
||||
"label": "CAS SSO",
|
||||
"dependencies": []
|
||||
},
|
||||
"authent-external\/3.2.1": {
|
||||
"label": "External user authentication",
|
||||
"dependencies": []
|
||||
},
|
||||
"authent-ldap\/3.2.1": {
|
||||
"label": "User authentication based on LDAP",
|
||||
"dependencies": []
|
||||
},
|
||||
"authent-local\/3.2.1": {
|
||||
"label": "User authentication based on the local DB",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-darkmoon-theme\/3.2.1": {
|
||||
"label": "Backoffice: Darkmoon theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-fullmoon-high-contrast-theme\/3.2.1": {
|
||||
"label": "Backoffice: Fullmoon with high contrast accessibility theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-fullmoon-protanopia-deuteranopia-theme\/3.2.1": {
|
||||
"label": "Backoffice: Fullmoon with protonopia & deuteranopia accessibility theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-fullmoon-tritanopia-theme\/3.2.1": {
|
||||
"label": "Backoffice: Fullmoon with tritanopia accessibility theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-db-tools\/3.2.1": {
|
||||
"label": "Database maintenance tools",
|
||||
"dependencies": [
|
||||
"itop-structure\/3.0.0"
|
||||
]
|
||||
},
|
||||
"itop-attachments\/3.2.1": {
|
||||
"label": "Tickets Attachments",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-backup\/3.2.1": {
|
||||
"label": "Backup utilities",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-bridge-cmdb-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1"
|
||||
]
|
||||
},
|
||||
"itop-bridge-cmdb-ticket\/3.2.1": {
|
||||
"label": "Bridge for CMDB and Ticket",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-tickets\/2.7.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-datacenter-mgmt-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB Virtualization objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-datacenter-mgmt\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-endusers-devices-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB endusers objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-endusers-devices\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-storage-mgmt-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB Virtualization objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-storage-mgmt\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-virtualization-mgmt-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB Virtualization objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-virtualization-mgmt\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-virtualization-storage\/3.2.1": {
|
||||
"label": "Links between virtualization and storage",
|
||||
"dependencies": [
|
||||
"itop-storage-mgmt\/2.2.0",
|
||||
"itop-virtualization-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-change-mgmt-itil\/3.2.1": {
|
||||
"label": "Change Management ITIL",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0",
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-change-mgmt\/3.2.1": {
|
||||
"label": "Change Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0",
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-config-mgmt\/3.2.1": {
|
||||
"label": "Configuration Management (CMDB)",
|
||||
"dependencies": [
|
||||
"itop-structure\/2.7.1"
|
||||
]
|
||||
},
|
||||
"itop-config\/3.2.1": {
|
||||
"label": "Configuration editor",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-core-update\/3.2.1": {
|
||||
"label": "iTop Core Update",
|
||||
"dependencies": [
|
||||
"itop-files-information\/2.7.0",
|
||||
"combodo-db-tools\/2.7.0"
|
||||
]
|
||||
},
|
||||
"itop-datacenter-mgmt\/3.2.1": {
|
||||
"label": "Datacenter Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-endusers-devices\/3.2.1": {
|
||||
"label": "End-user Devices Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-faq-light\/3.2.1": {
|
||||
"label": "Frequently Asked Questions Database",
|
||||
"dependencies": [
|
||||
"itop-structure\/3.0.0 || itop-portal\/3.0.0"
|
||||
]
|
||||
},
|
||||
"itop-files-information\/3.2.1": {
|
||||
"label": "iTop files information",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-full-itil\/3.2.1": {
|
||||
"label": "Bridge - Request management ITIL + Incident management ITIL",
|
||||
"dependencies": [
|
||||
"itop-request-mgmt-itil\/2.3.0",
|
||||
"itop-incident-mgmt-itil\/2.3.0"
|
||||
]
|
||||
},
|
||||
"itop-hub-connector\/3.2.1": {
|
||||
"label": "iTop Hub Connector",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-incident-mgmt-itil\/3.2.1": {
|
||||
"label": "Incident Management ITIL",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0",
|
||||
"itop-tickets\/2.4.0",
|
||||
"itop-profiles-itil\/2.3.0"
|
||||
]
|
||||
},
|
||||
"itop-knownerror-mgmt\/3.2.1": {
|
||||
"label": "Known Errors Database",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-oauth-client\/3.2.1": {
|
||||
"label": "OAuth 2.0 client",
|
||||
"dependencies": [
|
||||
"itop-welcome-itil\/3.1.0,"
|
||||
]
|
||||
},
|
||||
"itop-portal-base\/3.2.1": {
|
||||
"label": "Portal Development Library",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-portal\/3.2.1": {
|
||||
"label": "Enhanced Customer Portal",
|
||||
"dependencies": [
|
||||
"itop-portal-base\/2.7.0"
|
||||
]
|
||||
},
|
||||
"itop-problem-mgmt\/3.2.1": {
|
||||
"label": "Problem Management",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-profiles-itil\/3.2.1": {
|
||||
"label": "Create standard ITIL profiles",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-request-mgmt-itil\/3.2.1": {
|
||||
"label": "User request Management ITIL",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-request-mgmt\/3.2.1": {
|
||||
"label": "Simple Ticket Management",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-service-mgmt-provider\/3.2.1": {
|
||||
"label": "Service Management for Service Providers",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-service-mgmt\/3.2.1": {
|
||||
"label": "Service Management",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-sla-computation\/3.2.1": {
|
||||
"label": "SLA Computation",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-storage-mgmt\/3.2.1": {
|
||||
"label": "Advanced Storage Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-structure\/3.2.1": {
|
||||
"label": "Core iTop Structure",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-themes-compat\/3.2.1": {
|
||||
"label": "Light grey and Test red themes compatibility",
|
||||
"dependencies": [
|
||||
"itop-structure\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-tickets\/3.2.1": {
|
||||
"label": "Tickets Management",
|
||||
"dependencies": [
|
||||
"itop-structure\/2.7.1"
|
||||
]
|
||||
},
|
||||
"itop-virtualization-mgmt\/3.2.1": {
|
||||
"label": "Virtualization Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-welcome-itil\/3.2.1": {
|
||||
"label": "ITIL skin",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
["authent-cas\/3.2.1","authent-external\/3.2.1","authent-ldap\/3.2.1","authent-local\/3.2.1","combodo-backoffice-darkmoon-theme\/3.2.1","combodo-backoffice-fullmoon-high-contrast-theme\/3.2.1","combodo-backoffice-fullmoon-protanopia-deuteranopia-theme\/3.2.1","combodo-backoffice-fullmoon-tritanopia-theme\/3.2.1","itop-attachments\/3.2.1","itop-backup\/3.2.1","itop-config\/3.2.1","itop-files-information\/3.2.1","itop-portal-base\/3.2.1","itop-portal\/3.2.1","itop-profiles-itil\/3.2.1","itop-sla-computation\/3.2.1","itop-structure\/3.2.1","itop-themes-compat\/3.2.1","itop-tickets\/3.2.1","itop-welcome-itil\/3.2.1","combodo-db-tools\/3.2.1","itop-config-mgmt\/3.2.1","itop-core-update\/3.2.1","itop-datacenter-mgmt\/3.2.1","itop-endusers-devices\/3.2.1","itop-faq-light\/3.2.1","itop-hub-connector\/3.2.1","itop-incident-mgmt-itil\/3.2.1","itop-knownerror-mgmt\/3.2.1","itop-oauth-client\/3.2.1","itop-problem-mgmt\/3.2.1","itop-request-mgmt-itil\/3.2.1","itop-request-mgmt\/3.2.1","itop-service-mgmt-provider\/3.2.1","itop-service-mgmt\/3.2.1","itop-storage-mgmt\/3.2.1","itop-virtualization-mgmt\/3.2.1","itop-bridge-cmdb-services\/3.2.1","itop-bridge-cmdb-ticket\/3.2.1","itop-bridge-datacenter-mgmt-services\/3.2.1","itop-bridge-endusers-devices-services\/3.2.1","itop-bridge-storage-mgmt-services\/3.2.1","itop-bridge-virtualization-mgmt-services\/3.2.1","itop-bridge-virtualization-storage\/3.2.1","itop-change-mgmt-itil\/3.2.1","itop-change-mgmt\/3.2.1","itop-full-itil\/3.2.1"]
|
||||
File diff suppressed because one or more lines are too long
@@ -12,10 +12,8 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
public const USE_TRANSACTION = false;
|
||||
|
||||
private $sCookieFile = "";
|
||||
private $sUrl;
|
||||
private $sLogin;
|
||||
private $sPassword = "Iuytrez9876543ç_è-(";
|
||||
protected $sConfigTmpBackupFile;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
@@ -24,16 +22,13 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->sConfigTmpBackupFile = tempnam(sys_get_temp_dir(), "config_");
|
||||
MetaModel::GetConfig()->WriteToFile($this->sConfigTmpBackupFile);
|
||||
$this->BackupConfiguration();
|
||||
|
||||
$this->sLogin = "rest-user-".date('dmYHis');
|
||||
$this->CreateTestOrganization();
|
||||
|
||||
$this->sCookieFile = tempnam(sys_get_temp_dir(), 'jsondata_');
|
||||
|
||||
$this->sUrl = \MetaModel::GetConfig()->Get('app_root_url');
|
||||
|
||||
$oRestProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", ['name' => 'REST Services User'], true);
|
||||
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", ['name' => 'Administrator'], true);
|
||||
|
||||
@@ -47,16 +42,6 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
if (! is_null($this->sConfigTmpBackupFile) && is_file($this->sConfigTmpBackupFile)) {
|
||||
//put config back
|
||||
$sConfigPath = MetaModel::GetConfig()->GetLoadedFile();
|
||||
@chmod($sConfigPath, 0770);
|
||||
$oConfig = new Config($this->sConfigTmpBackupFile);
|
||||
$oConfig->WriteToFile($sConfigPath);
|
||||
@chmod($sConfigPath, 0444);
|
||||
unlink($this->sConfigTmpBackupFile);
|
||||
}
|
||||
|
||||
if (!empty($this->sCookieFile)) {
|
||||
unlink($this->sCookieFile);
|
||||
}
|
||||
@@ -150,26 +135,18 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
*/
|
||||
private function SendHTTPRequestWithCookies($sUri, $aPostFields, $sForcedLoginMode = null): string
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_COOKIEJAR, $this->sCookieFile);
|
||||
curl_setopt($ch, CURLOPT_COOKIEFILE, $this->sCookieFile);
|
||||
|
||||
$sUrl = "$this->sUrl/$sUri";
|
||||
if (!is_null($sForcedLoginMode)) {
|
||||
$sUrl .= "?login_mode=$sForcedLoginMode";
|
||||
$sUri .= "?login_mode=$sForcedLoginMode";
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, $sUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
$sResponse = curl_exec($ch);
|
||||
$aCurlOptions = [
|
||||
CURLOPT_COOKIEJAR => $this->sCookieFile,
|
||||
CURLOPT_COOKIEFILE => $this->sCookieFile,
|
||||
CURLOPT_HEADER => 1,
|
||||
];
|
||||
|
||||
$sResponse = $this->CallItopUri($sUri, $aPostFields, $aCurlOptions);
|
||||
var_dump($this->aLastCurlGetInfo);
|
||||
/** $sResponse example
|
||||
* "HTTP/1.1 200 OK
|
||||
Date: Wed, 07 Jun 2023 05:00:40 GMT
|
||||
@@ -177,16 +154,15 @@ class CliResetSessionTest extends ItopDataTestCase
|
||||
Set-Cookie: itop-2e83d2e9b00e354fdc528621cac532ac=q7ldcjq0rvbn33ccr9q8u8e953; path=/
|
||||
*/
|
||||
//var_dump($sResponse);
|
||||
$iHeaderSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$iHeaderSize = $this->aLastCurlGetInfo['header_size'] ?? 0;
|
||||
$sBody = substr($sResponse, $iHeaderSize);
|
||||
|
||||
//$iHttpCode = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
|
||||
if (preg_match('/HTTP.* (\d*) /', $sResponse, $aMatches)) {
|
||||
$sHttpCode = $aMatches[1];
|
||||
} else {
|
||||
$sHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$sHttpCode = $this->aLastCurlGetInfo['http_code'] ?? -1;
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
$this->assertEquals(200, $sHttpCode, "The test logic assumes that the HTTP request is correctly handled");
|
||||
return $sBody;
|
||||
|
||||
@@ -17,7 +17,6 @@ class RestTest extends ItopDataTestCase
|
||||
public const USE_TRANSACTION = false;
|
||||
public const CREATE_TEST_ORG = false;
|
||||
|
||||
private static $sUrl;
|
||||
private static $sLogin;
|
||||
private static $sPassword = "Iuytrez9876543ç_è-(";
|
||||
|
||||
@@ -44,7 +43,6 @@ class RestTest extends ItopDataTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
static::$sUrl = MetaModel::GetConfig()->Get('app_root_url');
|
||||
static::$sLogin = "rest-user-".date('dmYHis');
|
||||
|
||||
$this->CreateTestOrganization();
|
||||
@@ -96,7 +94,6 @@ class RestTest extends ItopDataTestCase
|
||||
|
||||
public function testPostJSONDataAsCurlFile()
|
||||
{
|
||||
$sCallbackName = 'fooCallback';
|
||||
$sJsonData = '{"operation": "list_operations"}';
|
||||
|
||||
// Test regular JSON result
|
||||
@@ -297,16 +294,7 @@ JSON;
|
||||
$aPostFields['callback'] = $sCallbackName;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, static::$sUrl."/webservices/rest.php");
|
||||
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// Force disable of certificate check as most of dev / test env have a self-signed certificate
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
$sJson = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$sJson = $this->CallItopUri('webservices/rest.php', $aPostFields);
|
||||
|
||||
if (!is_null($sTmpFile)) {
|
||||
unlink($sTmpFile);
|
||||
|
||||
Reference in New Issue
Block a user