mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-20 10:54:12 +01:00
Compare commits
48 Commits
detached
...
issue/jeff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2901a5d3 | ||
|
|
5ced223426 | ||
|
|
346daa3ccc | ||
|
|
afdfb2acf1 | ||
|
|
e58e699288 | ||
|
|
3955b4eb22 | ||
|
|
3d46fe6ef1 | ||
|
|
4dba47798c | ||
|
|
9480c4053d | ||
|
|
9ea197148c | ||
|
|
49a7f3118d | ||
|
|
bf80b5dca2 | ||
|
|
09afcb229c | ||
|
|
92f843f676 | ||
|
|
3df4ddc696 | ||
|
|
d552727c55 | ||
|
|
1fe401c102 | ||
|
|
a4b0b6e855 | ||
|
|
545028d68a | ||
|
|
6cb08ba361 | ||
|
|
b3cd79605d | ||
|
|
f9db405343 | ||
|
|
4f1f144c51 | ||
|
|
ef8ade5d20 | ||
|
|
cef4a52081 | ||
|
|
dc9fb2d693 | ||
|
|
e9ffbe5b09 | ||
|
|
9c792a601f | ||
|
|
cda6c1dfa8 | ||
|
|
0b242d872a | ||
|
|
c144c80663 | ||
|
|
d9261b8342 | ||
|
|
df567fb9fe | ||
|
|
028767768c | ||
|
|
11ec80830e | ||
|
|
edf992ef2b | ||
|
|
7c8fb1a51d | ||
|
|
aa27b3601b | ||
|
|
73f868ac83 | ||
|
|
5a2157ba21 | ||
|
|
03e25a226e | ||
|
|
24048d2b9c | ||
|
|
d8121b563a | ||
|
|
f266f5ff36 | ||
|
|
4a6b129eb8 | ||
|
|
4187f552a9 | ||
|
|
7f7ce0837e | ||
|
|
7df09541ac |
@@ -537,6 +537,12 @@ class CMDBSource
|
||||
*/
|
||||
public static function Query($sSQLQuery)
|
||||
{
|
||||
if (self::$sRaisesExceptionMsgWhenSqlQuery) {
|
||||
$e = new \Exception(self::$sRaisesExceptionMsgWhenSqlQuery);
|
||||
\IssueLog::Error(__METHOD__, null, [$e->getTraceAsString()]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (preg_match('/^START TRANSACTION;?$/i', $sSQLQuery)) {
|
||||
self::StartTransaction();
|
||||
|
||||
@@ -556,6 +562,13 @@ class CMDBSource
|
||||
return self::DBQuery($sSQLQuery);
|
||||
}
|
||||
|
||||
public static ?string $sRaisesExceptionMsgWhenSqlQuery = null;
|
||||
|
||||
public static function TriggerExceptionWhenSqlQuery(?string $sMsg)
|
||||
{
|
||||
self::$sRaisesExceptionMsgWhenSqlQuery = $sMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the query directly to the DB. **Be extra cautious with this !**
|
||||
*
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -122,9 +122,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (is_object($this->m_oSQLResult)) {
|
||||
$this->m_oSQLResult->free();
|
||||
}
|
||||
$this->Free();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -711,11 +709,8 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
|
||||
$sSQL = $this->_makeSelectQuery($this->m_aAttToLoad);
|
||||
|
||||
if (is_object($this->m_oSQLResult)) {
|
||||
// Free previous resultset if any
|
||||
$this->m_oSQLResult->free();
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
// Free previous resultset if any
|
||||
$this->Free();
|
||||
|
||||
try {
|
||||
$oKPI = new ExecutionKPI();
|
||||
@@ -871,23 +866,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
*/
|
||||
public function CountExceeds($iLimit)
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows)) {
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery([], $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, [], $iLimit + 2, 0, true);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if ($resQuery) {
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
$iCount = intval($aRow['COUNT']);
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
} else {
|
||||
$iCount = 0;
|
||||
}
|
||||
} else {
|
||||
$iCount = $this->m_iNumTotalDBRows;
|
||||
}
|
||||
|
||||
$iCount = $this->CountWithLimit($iLimit);
|
||||
return ($iCount > $iLimit);
|
||||
}
|
||||
|
||||
@@ -913,8 +892,8 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if ($resQuery) {
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
$iCount = intval($aRow['COUNT']);
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
} else {
|
||||
$iCount = 0;
|
||||
}
|
||||
@@ -935,6 +914,14 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
return $this->m_iNumLoadedDBRows + count($this->m_aAddedObjects);
|
||||
}
|
||||
|
||||
private function Free()
|
||||
{
|
||||
if (is_object($this->m_oSQLResult)) {
|
||||
CMDBSource::FreeResult($this->m_oSQLResult);
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an object (with the given class alias) at the current position in the set and move the cursor to the next position.
|
||||
*
|
||||
@@ -955,6 +942,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
}
|
||||
|
||||
if ($this->m_iCurrRow >= $this->CountLoaded()) {
|
||||
$this->Free();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -962,7 +950,9 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
$sRequestedClassAlias = $this->m_oFilter->GetClassAlias();
|
||||
}
|
||||
|
||||
if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) {
|
||||
if ($this->m_iCurrRow < count($this->m_aCacheObj)) {
|
||||
$oRetObj = $this->m_aCacheObj[$this->m_iCurrRow][$sRequestedClassAlias];
|
||||
} else if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) {
|
||||
// Pick the row from the database
|
||||
$aRow = CMDBSource::FetchArray($this->m_oSQLResult);
|
||||
foreach ($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass) {
|
||||
@@ -972,6 +962,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
} else {
|
||||
try {
|
||||
$oRetObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
|
||||
$this->m_aCacheObj[$this->m_iCurrRow] = [$sRequestedClassAlias => $oRetObj];
|
||||
} catch (CoreException $e) {
|
||||
$this->m_iCurrRow++;
|
||||
$oRetObj = $this->Fetch($sRequestedClassAlias);
|
||||
@@ -988,6 +979,8 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
return $oRetObj;
|
||||
}
|
||||
|
||||
private $m_aCacheObj = [];
|
||||
|
||||
/**
|
||||
* Fetch the whole row of objects (if several classes have been specified in the query) and move the cursor to the next position
|
||||
*
|
||||
@@ -1006,21 +999,29 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
}
|
||||
|
||||
if ($this->m_iCurrRow >= $this->CountLoaded()) {
|
||||
$this->Free();
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) {
|
||||
if ($this->m_iCurrRow < count($this->m_aCacheObj)) {
|
||||
$aRetObjects = $this->m_aCacheObj[$this->m_iCurrRow];
|
||||
} else if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) {
|
||||
// Pick the row from the database
|
||||
$aRow = CMDBSource::FetchArray($this->m_oSQLResult);
|
||||
$aRetObjects = [];
|
||||
foreach ($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass) {
|
||||
if (is_null($aRow[$sClassAlias.'id'])) {
|
||||
$oObj = null;
|
||||
} else {
|
||||
$oObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
|
||||
$oObj = null;
|
||||
if (!is_null($aRow[$sClassAlias.'id'])) {
|
||||
try {
|
||||
$oObj = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
|
||||
}
|
||||
catch (CoreException $e) {
|
||||
}
|
||||
}
|
||||
$aRetObjects[$sClassAlias] = $oObj;
|
||||
}
|
||||
|
||||
$this->m_aCacheObj[$this->m_iCurrRow] = $aRetObjects;
|
||||
} else {
|
||||
// Pick the row from the objects added *in memory*
|
||||
$aRetObjects = [];
|
||||
@@ -1063,6 +1064,10 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
$this->Load();
|
||||
}
|
||||
|
||||
if (is_null($this->m_oSQLResult)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->m_iCurrRow = min($iRow, $this->Count());
|
||||
if ($this->m_iCurrRow < $this->m_iNumLoadedDBRows) {
|
||||
$this->m_oSQLResult->data_seek($this->m_iCurrRow);
|
||||
@@ -1236,7 +1241,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function HasSameContents(DBObjectSet $oObjectSet, $aExcludeColumns = [])
|
||||
public function HasSameContents(DBObjectSet $oObjectSet, $aExcludeColumns = []): bool
|
||||
{
|
||||
$oComparator = new DBObjectSetComparator($this, $oObjectSet, $aExcludeColumns);
|
||||
return $oComparator->SetsAreEquivalent();
|
||||
|
||||
@@ -415,12 +415,7 @@ abstract class User extends cmdbAbstractObject
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:CurrentProfilesHaveInsufficientRights');
|
||||
}
|
||||
$oAddon->ResetCache();
|
||||
|
||||
if (is_null($aCurrentProfiles)) {
|
||||
Session::IsSet('profile_list');
|
||||
} else {
|
||||
Session::Set('profile_list', $aCurrentProfiles);
|
||||
}
|
||||
Session::Set('profile_list', $aCurrentProfiles);
|
||||
}
|
||||
// Prevent an administrator to remove their own admin profile
|
||||
if (UserRights::IsAdministrator($this)) {
|
||||
|
||||
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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ class iTopExtension
|
||||
* @var bool
|
||||
*/
|
||||
public $bMarkedAsChosen;
|
||||
/**
|
||||
* If null, check if at least one module cannot be uninstalled
|
||||
* @var bool|null
|
||||
*/
|
||||
public ?bool $bCanBeUninstalled = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
@@ -91,6 +96,14 @@ class iTopExtension
|
||||
* @var string[]
|
||||
*/
|
||||
public $aMissingDependencies;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bInstalled = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bRemovedFromDisk = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -115,13 +128,14 @@ class iTopExtension
|
||||
* @since 3.3.0
|
||||
* @return bool
|
||||
*/
|
||||
public function CanBeUninstalled()
|
||||
public function CanBeUninstalled(): bool
|
||||
{
|
||||
if (!is_null($this->bCanBeUninstalled)) {
|
||||
return $this->bCanBeUninstalled;
|
||||
}
|
||||
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
|
||||
$bUninstallable = $aModuleInfo['uninstallable'] === 'yes';
|
||||
if (!$bUninstallable) {
|
||||
return false;
|
||||
}
|
||||
$this->bCanBeUninstalled = $aModuleInfo['uninstallable'] === 'yes';
|
||||
return $this->bCanBeUninstalled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -139,6 +153,11 @@ class iTopExtensionsMap
|
||||
* @return void
|
||||
*/
|
||||
protected $aExtensions;
|
||||
/**
|
||||
* The list of all currently installed extensions
|
||||
* @var array|null
|
||||
*/
|
||||
protected ?array $aInstalledExtensions = null;
|
||||
|
||||
/**
|
||||
* The list of directories browsed using the ReadDir method when building the map
|
||||
@@ -146,7 +165,7 @@ class iTopExtensionsMap
|
||||
*/
|
||||
protected $aScannedDirs;
|
||||
|
||||
public function __construct($sFromEnvironment = 'production', $bNormalizeOldExtensions = true, $aExtraDirs = [])
|
||||
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
|
||||
{
|
||||
$this->aExtensions = [];
|
||||
$this->aScannedDirs = [];
|
||||
@@ -155,9 +174,6 @@ class iTopExtensionsMap
|
||||
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
$this->CheckDependencies($sFromEnvironment);
|
||||
if ($bNormalizeOldExtensions) {
|
||||
$this->NormalizeOldExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,6 +229,7 @@ class iTopExtensionsMap
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->sCode = $aChoiceInfo['extension_code'];
|
||||
$oExtension->sLabel = $aChoiceInfo['title'];
|
||||
$oExtension->sDescription = $aChoiceInfo['description'];
|
||||
if (array_key_exists('modules', $aChoiceInfo)) {
|
||||
// Some wizard choices are not associated with any module
|
||||
$oExtension->aModules = $aChoiceInfo['modules'];
|
||||
@@ -261,7 +278,7 @@ class iTopExtensionsMap
|
||||
*
|
||||
* @return \iTopExtension|null
|
||||
*/
|
||||
public function Get(string $sExtensionCode): ?iTopExtension
|
||||
public function GetFromExtensionCode(string $sExtensionCode): ?iTopExtension
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode === $sExtensionCode) {
|
||||
@@ -341,7 +358,7 @@ class iTopExtensionsMap
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
|
||||
} else {
|
||||
// Not already inside an folder containing an 'extension.xml' file
|
||||
// Not already inside a folder containing an 'extension.xml' file
|
||||
|
||||
// Ignore non-visible modules and auto-select ones, since these are never prompted
|
||||
// as a choice to the end-user
|
||||
@@ -432,10 +449,19 @@ class iTopExtensionsMap
|
||||
return $this->aExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array All available extensions and extensions currently installed but not available due to files removal
|
||||
*/
|
||||
public function GetAllExtensionsWithPreviouslyInstalled(): array
|
||||
{
|
||||
//Mind the order, local extensions data must overwrite installed extensions data since installed extensions does not have the associated modules.
|
||||
return array_merge($this->aInstalledExtensions ?? [], $this->aExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given extension as chosen
|
||||
* @param string $sExtensionCode The code of the extension (code without verison number)
|
||||
* @param bool $bMark The value to set for the bmarkAschosen flag
|
||||
* @param string $sExtensionCode The code of the extension (code without version number)
|
||||
* @param bool $bMark The value to set for the bMarkAsChosen flag
|
||||
* @return void
|
||||
*/
|
||||
public function MarkAsChosen($sExtensionCode, $bMark = true)
|
||||
@@ -501,125 +527,56 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function LoadChoicesFromDatabase(Config $oConfig)
|
||||
{
|
||||
try {
|
||||
$aInstalledExtensions = [];
|
||||
if (CMDBSource::DBName() === null) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
}
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_extension_install");
|
||||
$aInstalledExtensions = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($aInstalledExtensions as $aDBInfo) {
|
||||
$this->MarkAsChosen($aDBInfo['code']);
|
||||
$this->SetInstalledVersion($aDBInfo['code'], $aDBInfo['version']);
|
||||
foreach ($this->LoadInstalledExtensionsFromDatabase($oConfig) as $oExtension) {
|
||||
$this->MarkAsChosen($oExtension->sCode);
|
||||
$this->SetInstalledVersion($oExtension->sCode, $oExtension->sVersion);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find is a single-module extension is contained within another extension
|
||||
* @param iTopExtension $oExtension
|
||||
* @return NULL|iTopExtension
|
||||
*/
|
||||
public function IsExtensionObsoletedByAnother(iTopExtension $oExtension)
|
||||
protected function LoadInstalledExtensionsFromDatabase(Config $oConfig): array|false
|
||||
{
|
||||
// Complex extensions (more than 1 module) are never considered as obsolete
|
||||
if (count($oExtension->aModules) != 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->GetAllExtensions() as $oOtherExtension) {
|
||||
if (($oOtherExtension->sSourceDir != $oExtension->sSourceDir) && ($oOtherExtension->sSource != iTopExtension::SOURCE_WIZARD)) {
|
||||
if (array_key_exists($oExtension->sCode, $oOtherExtension->aModuleVersion) &&
|
||||
(version_compare($oOtherExtension->aModuleVersion[$oExtension->sCode], $oExtension->sVersion, '>='))) {
|
||||
// Found another extension containing a more recent version of the extension/module
|
||||
return $oOtherExtension;
|
||||
}
|
||||
try {
|
||||
if (CMDBSource::DBName() === null) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
}
|
||||
}
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_extension_install");
|
||||
$aDBInfo = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
|
||||
|
||||
// No match at all
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for multi-module extensions that are NOT deployed as an extension (i.e. shipped with an extension.xml file)
|
||||
* but as a bunch of un-related modules based on the signature of some well-known extensions. If such an extension is found,
|
||||
* replace the stand-alone modules by an "extension" with the appropriate label/description/version containing the same modules.
|
||||
* @param string $sInSourceOnly The source directory to scan (datamodel|extensions|data)
|
||||
*/
|
||||
public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MANUAL)
|
||||
{
|
||||
$aSignatures = $this->GetOldExtensionsSignatures();
|
||||
foreach ($aSignatures as $sExtensionCode => $aExtensionSignatures) {
|
||||
$bFound = false;
|
||||
foreach ($aExtensionSignatures['versions'] as $sVersion => $aModules) {
|
||||
$bInstalled = true;
|
||||
foreach ($aModules as $sModuleId) {
|
||||
if (!$this->ModuleIsPresent($sModuleId, $sInSourceOnly)) {
|
||||
$bFound = false;
|
||||
break; // One missing module is enough to determine that the extension/version is not present
|
||||
} else {
|
||||
$bInstalled = $bInstalled && $this->ModuleIsInstalled($sModuleId, $sInSourceOnly);
|
||||
$bFound = true;
|
||||
}
|
||||
}
|
||||
if ($bFound) {
|
||||
break;
|
||||
} // The current version matches the signature
|
||||
}
|
||||
|
||||
if ($bFound) {
|
||||
$this->aInstalledExtensions = [];
|
||||
foreach ($aDBInfo as $aExtensionInfo) {
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->sCode = $sExtensionCode;
|
||||
$oExtension->sLabel = $aExtensionSignatures['label'];
|
||||
$oExtension->sSource = $sInSourceOnly;
|
||||
$oExtension->sDescription = $aExtensionSignatures['description'];
|
||||
$oExtension->sVersion = $sVersion;
|
||||
$oExtension->sCode = $aExtensionInfo['code'];
|
||||
$oExtension->sLabel = $aExtensionInfo['label'];
|
||||
$oExtension->sDescription = $aExtensionInfo['description'] ?? '';
|
||||
$oExtension->sVersion = $aExtensionInfo['version'];
|
||||
$oExtension->sSource = $aExtensionInfo['source'];
|
||||
$oExtension->bMandatory = false;
|
||||
$oExtension->sMoreInfoUrl = '';
|
||||
$oExtension->aModules = [];
|
||||
if ($bInstalled) {
|
||||
$oExtension->sInstalledVersion = $sVersion;
|
||||
$oExtension->bMarkedAsChosen = true;
|
||||
$oExtension->aModuleVersion = [];
|
||||
$oExtension->aModuleInfo = [];
|
||||
$oExtension->sSourceDir = '';
|
||||
$oExtension->bVisible = true;
|
||||
$oExtension->bInstalled = true;
|
||||
$oExtension->bCanBeUninstalled = !isset($aExtensionInfo['uninstallable']) || $aExtensionInfo['uninstallable'] === 'yes';
|
||||
$oChoice = $this->GetFromExtensionCode($oExtension->sCode);
|
||||
if ($oChoice) {
|
||||
$oChoice->bInstalled = true;
|
||||
} else {
|
||||
$oExtension->bRemovedFromDisk = true;
|
||||
}
|
||||
foreach ($aModules as $sModuleId) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
$oExtension->aModules[] = $sModuleName;
|
||||
$oExtension->aModuleInfo[$sModuleName] = $this->aExtensions[$sModuleId]->aModuleInfo[$sModuleName];
|
||||
}
|
||||
$this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension);
|
||||
|
||||
$this->aInstalledExtensions[$oExtension->sCode.'/'.$oExtension->sVersion] = $oExtension;
|
||||
}
|
||||
|
||||
return $this->aInstalledExtensions;
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given module-code/version is present on the disk
|
||||
* @param string $sModuleIdToFind The module ID (code/version) to search for
|
||||
* @param string $sInSourceOnly The origin (=source) to search in (datamodel|extensions|data)
|
||||
* @return boolean
|
||||
*/
|
||||
protected function ModuleIsPresent($sModuleIdToFind, $sInSourceOnly)
|
||||
{
|
||||
return (array_key_exists($sModuleIdToFind, $this->aExtensions) && ($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given module-code/version is currently installed
|
||||
* @param string $sModuleIdToFind The module ID (code/version) to search for
|
||||
* @param string $sInSourceOnly The origin (=source) to search in (datamodel|extensions|data)
|
||||
* @return boolean
|
||||
*/
|
||||
protected function ModuleIsInstalled($sModuleIdToFind, $sInSourceOnly)
|
||||
{
|
||||
return (array_key_exists($sModuleIdToFind, $this->aExtensions) &&
|
||||
($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly) &&
|
||||
($this->aExtensions[$sModuleIdToFind]->sInstalledVersion !== ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given module name is "chosen" since it is part of a "chosen" extension (in the specified source dir)
|
||||
* @param string $sModuleNameToFind
|
||||
@@ -640,657 +597,4 @@ class iTopExtensionsMap
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a given set of stand-alone modules by one single "extension"
|
||||
* @param string[] $aModules
|
||||
* @param iTopExtension $oNewExtension
|
||||
*/
|
||||
protected function ReplaceModulesByNormalizedExtension($aModules, iTopExtension $oNewExtension)
|
||||
{
|
||||
foreach ($aModules as $sModuleId) {
|
||||
unset($this->aExtensions[$sModuleId]);
|
||||
}
|
||||
$this->AddExtension($oNewExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of signatures of some well-known multi-module extensions without extension.xml file (should not exist anymore)
|
||||
*
|
||||
* @return string[][]|string[][][][]
|
||||
*/
|
||||
protected function GetOldExtensionsSignatures()
|
||||
{
|
||||
// Generated by the Factory using the page export_component_versions_for_normalisation.php
|
||||
return [
|
||||
'combodo-approval-process-light' =>
|
||||
[
|
||||
'label' => 'Approval process light',
|
||||
'description' => 'Approve a request via a simple email',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.0',
|
||||
1 => 'combodo-approval-light/1.0.1',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.1',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.2',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.2',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.3',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-light/1.0.3',
|
||||
],
|
||||
'1.2.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.3.0',
|
||||
1 => 'combodo-approval-light/1.0.3',
|
||||
],
|
||||
'1.2.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.0',
|
||||
1 => 'combodo-approval-light/1.0.4',
|
||||
],
|
||||
'1.3.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-light/1.1.1',
|
||||
],
|
||||
'1.3.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-light/1.1.1',
|
||||
],
|
||||
'1.3.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.2.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-light/1.0.5',
|
||||
],
|
||||
'1.3.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.1',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.3.4' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.2',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.3.5' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.4.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
2 => 'itop-approval-portal/1.0.0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-approval-process-automation' =>
|
||||
[
|
||||
'label' => 'Approval process automation',
|
||||
'description' => 'Control your approval process with predefined rules based on service catalog',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.0',
|
||||
1 => 'combodo-approval-extended/1.0.2',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.1',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.2',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.2',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.3',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-extended/1.0.5',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-extended/1.0.6',
|
||||
],
|
||||
'1.2.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.3.0',
|
||||
1 => 'combodo-approval-extended/1.0.7',
|
||||
],
|
||||
'1.2.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.0',
|
||||
1 => 'combodo-approval-extended/1.0.8',
|
||||
],
|
||||
'1.3.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-extended/1.2.1',
|
||||
],
|
||||
'1.3.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-extended/1.2.1',
|
||||
],
|
||||
'1.3.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-extended/1.2.2',
|
||||
],
|
||||
'1.2.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-extended/1.0.9',
|
||||
],
|
||||
'1.3.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.1',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
],
|
||||
'1.3.4' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.2',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
],
|
||||
'1.3.5' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
],
|
||||
'1.4.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
3 => 'itop-approval-portal/1.0.0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-predefined-response-models' =>
|
||||
[
|
||||
'label' => 'Predefined response models',
|
||||
'description' => 'Pick common answers from a list of predefined replies grouped by categories to update tickets log',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.0' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.0',
|
||||
1 => 'precanned-replies-pro/1.0.0',
|
||||
],
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.1',
|
||||
1 => 'precanned-replies-pro/1.0.1',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.2',
|
||||
1 => 'precanned-replies-pro/1.0.1',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.3',
|
||||
1 => 'precanned-replies-pro/1.0.1',
|
||||
],
|
||||
'1.0.4' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.3',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
'1.0.5' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.4',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.1.0',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.1.1',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-customized-request-forms' =>
|
||||
[
|
||||
'label' => 'Customized request forms',
|
||||
'description' => 'Define personalized request forms based on the service catalog. Add extra fields for a given type of request.',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.1',
|
||||
1 => 'itop-request-template/1.0.0',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.2',
|
||||
1 => 'itop-request-template/1.0.0',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.2',
|
||||
1 => 'itop-request-template/1.0.1',
|
||||
],
|
||||
'1.0.4' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.3',
|
||||
1 => 'itop-request-template/1.0.1',
|
||||
],
|
||||
'1.0.5' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.4',
|
||||
1 => 'itop-request-template/1.0.1',
|
||||
],
|
||||
'2.0.0' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.0',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.1' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.1',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.2' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.2',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.3' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.4',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.4' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.5',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.5' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.6',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.6' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.8',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.7' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.9',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.8' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.12',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-sla-considering-business-hours' =>
|
||||
[
|
||||
'label' => 'SLA considering business hours',
|
||||
'description' => 'Compute SLAs taking into account service coverage window and holidays',
|
||||
'versions' =>
|
||||
[
|
||||
'2.0.1' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.0.1',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.0' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.0',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.1' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.1',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.2' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.2',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.3' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.2',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.0.2' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.0.1',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.1.4' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.3',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.1.5' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.5',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.1.6' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.5',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
'2.1.7' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.6',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
'2.1.8' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.7',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
'2.1.9' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.8',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-mail-to-ticket-automation' =>
|
||||
[
|
||||
'label' => 'Mail to ticket automation',
|
||||
'description' => 'Scan several mailboxes to create or update tickets.',
|
||||
'versions' =>
|
||||
[
|
||||
'2.6.0' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.0',
|
||||
1 => 'itop-standard-email-synchro/2.6.0',
|
||||
],
|
||||
'2.6.1' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.1',
|
||||
1 => 'itop-standard-email-synchro/2.6.0',
|
||||
],
|
||||
'2.6.2' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.2',
|
||||
1 => 'itop-standard-email-synchro/2.6.0',
|
||||
],
|
||||
'2.6.3' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.2',
|
||||
1 => 'itop-standard-email-synchro/2.6.1',
|
||||
],
|
||||
'2.6.4' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.3',
|
||||
1 => 'itop-standard-email-synchro/2.6.2',
|
||||
],
|
||||
'2.6.5' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.4',
|
||||
1 => 'itop-standard-email-synchro/2.6.2',
|
||||
],
|
||||
'2.6.6' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.5',
|
||||
1 => 'itop-standard-email-synchro/2.6.3',
|
||||
],
|
||||
'2.6.7' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.6',
|
||||
1 => 'itop-standard-email-synchro/2.6.4',
|
||||
],
|
||||
'2.6.8' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.7',
|
||||
1 => 'itop-standard-email-synchro/2.6.4',
|
||||
],
|
||||
'2.6.9' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.8',
|
||||
1 => 'itop-standard-email-synchro/2.6.5',
|
||||
],
|
||||
'2.6.10' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.9',
|
||||
1 => 'itop-standard-email-synchro/2.6.6',
|
||||
],
|
||||
'2.6.11' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.10',
|
||||
1 => 'itop-standard-email-synchro/2.6.6',
|
||||
],
|
||||
'2.6.12' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.11',
|
||||
1 => 'itop-standard-email-synchro/2.6.6',
|
||||
],
|
||||
'3.0.0' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.0',
|
||||
1 => 'itop-standard-email-synchro/3.0.0',
|
||||
],
|
||||
'3.0.1' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.1',
|
||||
1 => 'itop-standard-email-synchro/3.0.1',
|
||||
],
|
||||
'3.0.2' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.2',
|
||||
1 => 'itop-standard-email-synchro/3.0.1',
|
||||
],
|
||||
'3.0.3' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.3',
|
||||
1 => 'itop-standard-email-synchro/3.0.3',
|
||||
],
|
||||
'3.0.4' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.3',
|
||||
1 => 'itop-standard-email-synchro/3.0.4',
|
||||
],
|
||||
'3.0.5' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.4',
|
||||
1 => 'itop-standard-email-synchro/3.0.4',
|
||||
],
|
||||
'3.0.6' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.5',
|
||||
1 => 'itop-standard-email-synchro/3.0.4',
|
||||
],
|
||||
'3.0.7' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.5',
|
||||
1 => 'itop-standard-email-synchro/3.0.5',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-configurator-for-automatic-object-creation' =>
|
||||
[
|
||||
'label' => 'Configurator for automatic object creation',
|
||||
'description' => 'Templating based on existing objects.',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.13' =>
|
||||
[
|
||||
1 => 'itop-stencils/1.0.6',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-user-actions-configurator' =>
|
||||
[
|
||||
'label' => 'User actions configurator',
|
||||
'description' => 'Configure user actions to simplify and automate processes (e.g. create an incident from a CI).',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.0' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.0',
|
||||
],
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.1',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.2',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.3',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.0',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.1',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.2',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.3',
|
||||
],
|
||||
'1.1.4' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.4',
|
||||
],
|
||||
'1.1.5' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.5',
|
||||
],
|
||||
'1.1.6' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.6',
|
||||
],
|
||||
'1.1.7' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.7',
|
||||
],
|
||||
'1.1.8' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.8',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-send-updates-by-email' =>
|
||||
[
|
||||
'label' => 'Send updates by email',
|
||||
'description' => 'Send an email to pre-configured contacts when a ticket log is updated.',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'email-reply/1.0.1',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'email-reply/1.0.3',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.1',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.2',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.3',
|
||||
],
|
||||
'1.1.4' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.4',
|
||||
],
|
||||
'1.1.5' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.5',
|
||||
],
|
||||
'1.1.6' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.6',
|
||||
],
|
||||
'1.1.7' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.7',
|
||||
],
|
||||
// 1.1.8 was never released
|
||||
'1.1.9' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.9',
|
||||
],
|
||||
'1.1.10' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.10',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
146
setup/modulediscovery.class.inc.php
Normal file → Executable file
146
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
|
||||
{
|
||||
@@ -211,76 +215,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 +243,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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024 Combodo SAS
|
||||
//
|
||||
// This file is part of iTop.
|
||||
@@ -16,7 +17,6 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Manage a runtime environment
|
||||
*
|
||||
@@ -33,18 +33,16 @@ require_once APPROOT.'setup/modelfactory.class.inc.php';
|
||||
require_once APPROOT.'setup/compiler.class.inc.php';
|
||||
require_once APPROOT.'setup/extensionsmap.class.inc.php';
|
||||
|
||||
define ('MODULE_ACTION_OPTIONAL', 1);
|
||||
define ('MODULE_ACTION_MANDATORY', 2);
|
||||
define ('MODULE_ACTION_IMPOSSIBLE', 3);
|
||||
define ('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application
|
||||
define ('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel
|
||||
|
||||
|
||||
define('MODULE_ACTION_OPTIONAL', 1);
|
||||
define('MODULE_ACTION_MANDATORY', 2);
|
||||
define('MODULE_ACTION_IMPOSSIBLE', 3);
|
||||
define('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application
|
||||
define('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel
|
||||
|
||||
class RunTimeEnvironment
|
||||
{
|
||||
const STATIC_CALL_AUTOSELECT_WHITELIST=[
|
||||
"SetupInfo::ModuleIsSelected"
|
||||
public const STATIC_CALL_AUTOSELECT_WHITELIST = [
|
||||
"SetupInfo::ModuleIsSelected",
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -74,13 +72,10 @@ class RunTimeEnvironment
|
||||
public function __construct($sEnvironment = 'production', $bAutoCommit = true)
|
||||
{
|
||||
$this->sFinalEnv = $sEnvironment;
|
||||
if ($bAutoCommit)
|
||||
{
|
||||
if ($bAutoCommit) {
|
||||
// Build directly onto the requested environment
|
||||
$this->sTargetEnv = $sEnvironment;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Build into a temporary target
|
||||
$this->sTargetEnv = $sEnvironment.'-build';
|
||||
}
|
||||
@@ -121,25 +116,20 @@ class RunTimeEnvironment
|
||||
require_once APPROOT.'/setup/moduleinstallation.class.inc.php';
|
||||
|
||||
$sConfigFile = $oConfig->GetLoadedFile();
|
||||
if (strlen($sConfigFile) > 0)
|
||||
{
|
||||
if (strlen($sConfigFile) > 0) {
|
||||
$this->log_info("MetaModel::Startup from $sConfigFile (ModelOnly = $bModelOnly)");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->log_info("MetaModel::Startup (ModelOnly = $bModelOnly)");
|
||||
}
|
||||
|
||||
if (!$bUseCache)
|
||||
{
|
||||
if (!$bUseCache) {
|
||||
// Reset the cache for the first use !
|
||||
MetaModel::ResetAllCaches($this->sTargetEnv);
|
||||
}
|
||||
|
||||
MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sTargetEnv);
|
||||
|
||||
if ($this->oExtensionsMap === null)
|
||||
{
|
||||
if ($this->oExtensionsMap === null) {
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
|
||||
}
|
||||
}
|
||||
@@ -178,26 +168,23 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
$aRes = array(
|
||||
ROOT_MODULE => array(
|
||||
$aRes = [
|
||||
ROOT_MODULE => [
|
||||
'version_db' => '',
|
||||
'name_db' => '',
|
||||
'version_code' => ITOP_VERSION_FULL,
|
||||
'name_code' => ITOP_APPLICATION,
|
||||
)
|
||||
);
|
||||
],
|
||||
];
|
||||
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath);
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
|
||||
$aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
foreach($aModules as $sModuleId => $aModuleInfo)
|
||||
{
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if ($sModuleName == '')
|
||||
{
|
||||
if ($sModuleName == '') {
|
||||
throw new Exception("Missing name for the module: '$sModuleId'");
|
||||
}
|
||||
if ($sModuleVersion == '')
|
||||
{
|
||||
if ($sModuleVersion == '') {
|
||||
// The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not)
|
||||
//throw new Exception("Missing version for the module: '$sModuleId'");
|
||||
$sModuleVersion = '1.0.0';
|
||||
@@ -207,95 +194,76 @@ class RunTimeEnvironment
|
||||
$aModuleInfo['version_db'] = '';
|
||||
$aModuleInfo['version_code'] = $sModuleVersion;
|
||||
|
||||
if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2')))
|
||||
{
|
||||
if (!in_array($sModuleAppVersion, ['1.0.0', '1.0.1', '1.0.2'])) {
|
||||
// This module is NOT compatible with the current version
|
||||
$aModuleInfo['install'] = array(
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is not compatible with the current version of the application'
|
||||
);
|
||||
}
|
||||
elseif ($aModuleInfo['mandatory'])
|
||||
{
|
||||
$aModuleInfo['install'] = array(
|
||||
'message' => 'the module is not compatible with the current version of the application',
|
||||
];
|
||||
} elseif ($aModuleInfo['mandatory']) {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_MANDATORY,
|
||||
'message' => 'the module is part of the application'
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aModuleInfo['install'] = array(
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => ''
|
||||
);
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
$aRes[$sModuleName] = $aModuleInfo;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$aSelectInstall = array();
|
||||
try {
|
||||
$aSelectInstall = [];
|
||||
if (! is_null($oConfig)) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
}
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
}
|
||||
|
||||
// Build the list of installed module (get the latest installation)
|
||||
//
|
||||
$aInstallByModule = array(); // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
$iRootId = 0;
|
||||
foreach ($aSelectInstall as $aInstall)
|
||||
{
|
||||
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel'))
|
||||
{
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel')) {
|
||||
// Root module, what is its ID ?
|
||||
$iId = (int) $aInstall['id'];
|
||||
if ($iId > $iRootId)
|
||||
{
|
||||
if ($iId > $iRootId) {
|
||||
$iRootId = $iId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aSelectInstall as $aInstall)
|
||||
{
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
//$aInstall['comment']; // unsused
|
||||
$iInstalled = strtotime($aInstall['installed']);
|
||||
$sModuleName = $aInstall['name'];
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '')
|
||||
{
|
||||
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['parent_id'] == 0) {
|
||||
$sModuleName = ROOT_MODULE;
|
||||
}
|
||||
else if($aInstall['parent_id'] != $iRootId)
|
||||
{
|
||||
} elseif ($aInstall['parent_id'] != $iRootId) {
|
||||
// Skip all modules belonging to previous installations
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, $aInstallByModule))
|
||||
{
|
||||
if ($iInstalled < $aInstallByModule[$sModuleName]['installed'])
|
||||
{
|
||||
if (array_key_exists($sModuleName, $aInstallByModule)) {
|
||||
if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0)
|
||||
{
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$aRes[$sModuleName]['version_db'] = $sModuleVersion;
|
||||
$aRes[$sModuleName]['name_db'] = $aInstall['name'];
|
||||
}
|
||||
@@ -306,37 +274,33 @@ class RunTimeEnvironment
|
||||
|
||||
// Adjust the list of proposed modules
|
||||
//
|
||||
foreach ($aInstallByModule as $sModuleName => $aModuleDB)
|
||||
{
|
||||
if ($sModuleName == ROOT_MODULE) continue; // Skip the main module
|
||||
foreach ($aInstallByModule as $sModuleName => $aModuleDB) {
|
||||
if ($sModuleName == ROOT_MODULE) {
|
||||
continue;
|
||||
} // Skip the main module
|
||||
|
||||
if (!array_key_exists($sModuleName, $aRes))
|
||||
{
|
||||
if (!array_key_exists($sModuleName, $aRes)) {
|
||||
// A module was installed, it is not proposed in the new build... skip
|
||||
continue;
|
||||
}
|
||||
$aRes[$sModuleName]['version_db'] = $aModuleDB['version'];
|
||||
|
||||
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY)
|
||||
{
|
||||
$aRes[$sModuleName]['uninstall'] = array(
|
||||
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY) {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is part of the application'
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRes[$sModuleName]['uninstall'] = array(
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => ''
|
||||
);
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Config $oConfig
|
||||
*
|
||||
@@ -359,10 +323,10 @@ class RunTimeEnvironment
|
||||
* Return an array with extra directories to scan for extensions/modules to install
|
||||
* @return string[]
|
||||
*/
|
||||
protected function GetExtraDirsToScan($aDirs = array())
|
||||
protected function GetExtraDirsToScan($aDirs = [])
|
||||
{
|
||||
// Do nothing, overload this method if needed
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,25 +345,22 @@ class RunTimeEnvironment
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
{
|
||||
$sSourceDirFull = APPROOT.$sSourceDir;
|
||||
if (!is_dir($sSourceDirFull))
|
||||
{
|
||||
if (!is_dir($sSourceDirFull)) {
|
||||
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
|
||||
}
|
||||
$aDirsToCompile = array($sSourceDirFull);
|
||||
if (is_dir(APPROOT.'extensions'))
|
||||
{
|
||||
$aDirsToCompile = [$sSourceDirFull];
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToCompile[] = APPROOT.'extensions';
|
||||
}
|
||||
$sExtraDir = utils::GetDataPath().$this->sTargetEnv.'-modules/';
|
||||
if (is_dir($sExtraDir))
|
||||
{
|
||||
if (is_dir($sExtraDir)) {
|
||||
$aDirsToCompile[] = $sExtraDir;
|
||||
}
|
||||
|
||||
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
|
||||
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
|
||||
|
||||
$aRet = array();
|
||||
$aRet = [];
|
||||
|
||||
// Determine the installed modules and extensions
|
||||
//
|
||||
@@ -412,12 +373,10 @@ class RunTimeEnvironment
|
||||
// mark as (automatically) chosen alll the "remote" modules present in the
|
||||
// target environment (data/<target-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, true, $aExtraDirs);
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, $aExtraDirs);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
|
||||
{
|
||||
if($this->IsExtensionSelected($oExtension))
|
||||
{
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if ($this->IsExtensionSelected($oExtension)) {
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
@@ -429,28 +388,23 @@ class RunTimeEnvironment
|
||||
|
||||
$oFactory = new ModelFactory($aDirsToCompile);
|
||||
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
|
||||
if (file_exists($sDeltaFile))
|
||||
{
|
||||
if (file_exists($sDeltaFile)) {
|
||||
$oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
|
||||
$aRet[$oCoreModule->GetName()] = $oCoreModule;
|
||||
}
|
||||
$sDeltaFile = APPROOT.'application/datamodel.application.xml';
|
||||
if (file_exists($sDeltaFile))
|
||||
{
|
||||
if (file_exists($sDeltaFile)) {
|
||||
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
|
||||
$aRet[$oApplicationModule->GetName()] = $oApplicationModule;
|
||||
}
|
||||
|
||||
$aModules = $oFactory->FindModules();
|
||||
foreach($aModules as $oModule)
|
||||
{
|
||||
foreach ($aModules as $oModule) {
|
||||
$sModule = $oModule->GetName();
|
||||
$sModuleRootDir = $oModule->GetRootDir();
|
||||
$bIsExtra = $this->oExtensionsMap->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
|
||||
if (array_key_exists($sModule, $aAvailableModules))
|
||||
{
|
||||
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) //Extra modules are always unless they are 'AutoSelect'
|
||||
{
|
||||
if (array_key_exists($sModule, $aAvailableModules)) {
|
||||
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) { //Extra modules are always unless they are 'AutoSelect'
|
||||
$aRet[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
}
|
||||
@@ -459,33 +413,27 @@ class RunTimeEnvironment
|
||||
$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
|
||||
// Now process the 'AutoSelect' modules
|
||||
do
|
||||
{
|
||||
do {
|
||||
// Loop while new modules are added...
|
||||
$bModuleAdded = false;
|
||||
foreach($aModules as $oModule)
|
||||
{
|
||||
if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect())
|
||||
{
|
||||
foreach ($aModules as $oModule) {
|
||||
if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect()) {
|
||||
SetupInfo::SetSelectedModules($aRet);
|
||||
try{
|
||||
try {
|
||||
$bSelected = $oPhpExpressionEvaluator->ParseAndEvaluateBooleanExpression($oModule->GetAutoSelect());
|
||||
if ($bSelected)
|
||||
{
|
||||
if ($bSelected) {
|
||||
$aRet[$oModule->GetName()] = $oModule; // store the Id of the selected module
|
||||
$bModuleAdded = true;
|
||||
}
|
||||
} catch(ModuleFileReaderException $e){
|
||||
} catch (ModuleFileReaderException $e) {
|
||||
//do nothing. logged already
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while($bModuleAdded);
|
||||
} while ($bModuleAdded);
|
||||
|
||||
$sDeltaFile = utils::GetDataPath().$this->sTargetEnv.'.delta.xml';
|
||||
if (file_exists($sDeltaFile))
|
||||
{
|
||||
if (file_exists($sDeltaFile)) {
|
||||
$oDelta = new MFDeltaModule($sDeltaFile);
|
||||
$aRet[$oDelta->GetName()] = $oDelta;
|
||||
}
|
||||
@@ -514,10 +462,8 @@ class RunTimeEnvironment
|
||||
//
|
||||
$oFactory = new ModelFactory($sSourceDirFull);
|
||||
$aModulesToCompile = $this->GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
foreach ($aModulesToCompile as $oModule)
|
||||
{
|
||||
if ($oModule instanceof MFDeltaModule)
|
||||
{
|
||||
foreach ($aModulesToCompile as $oModule) {
|
||||
if ($oModule instanceof MFDeltaModule) {
|
||||
// Just before loading the delta, let's save an image of the datamodel
|
||||
// in case there is no delta the operation will be done after the end of the loop
|
||||
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$this->sTargetEnv.'.xml');
|
||||
@@ -525,7 +471,6 @@ class RunTimeEnvironment
|
||||
$oFactory->LoadModule($oModule);
|
||||
}
|
||||
|
||||
|
||||
if ($oModule instanceof MFDeltaModule) {
|
||||
// A delta was loaded, let's save a second copy of the datamodel
|
||||
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$this->sTargetEnv.'-with-delta.xml');
|
||||
@@ -560,45 +505,32 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function CreateDatabaseStructure(Config $oConfig, $sMode)
|
||||
{
|
||||
if (strlen($oConfig->Get('db_subname')) > 0)
|
||||
{
|
||||
if (strlen($oConfig->Get('db_subname')) > 0) {
|
||||
$this->log_info("Creating the structure in '".$oConfig->Get('db_name')."' (table names prefixed by '".$oConfig->Get('db_subname')."').");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->log_info("Creating the structure in '".$oConfig->Get('db_name')."'.");
|
||||
}
|
||||
|
||||
//MetaModel::CheckDefinitions();
|
||||
if ($sMode == 'install')
|
||||
{
|
||||
if (!MetaModel::DBExists(/* bMustBeComplete */ false))
|
||||
{
|
||||
MetaModel::DBCreate(array($this, 'LogQueryCallback'));
|
||||
if ($sMode == 'install') {
|
||||
if (!MetaModel::DBExists(/* bMustBeComplete */ false)) {
|
||||
MetaModel::DBCreate([$this, 'LogQueryCallback']);
|
||||
$this->log_ok("Database structure successfully created.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strlen($oConfig->Get('db_subname')) > 0)
|
||||
{
|
||||
} else {
|
||||
if (strlen($oConfig->Get('db_subname')) > 0) {
|
||||
throw new Exception("Error: found iTop tables into the database '".$oConfig->Get('db_name')."' (prefix: '".$oConfig->Get('db_subname')."'). Please, try selecting another database instance or specify another prefix to prevent conflicting table names.");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
throw new Exception("Error: found iTop tables into the database '".$oConfig->Get('db_name')."'. Please, try selecting another database instance or specify a prefix to prevent conflicting table names.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MetaModel::DBExists(/* bMustBeComplete */ false))
|
||||
{
|
||||
} else {
|
||||
if (MetaModel::DBExists(/* bMustBeComplete */ false)) {
|
||||
// Have it work fine even if the DB has been set in read-only mode for the users
|
||||
// (fix copied from RunTimeEnvironment::RecordInstallation)
|
||||
$iPrevAccessMode = $oConfig->Get('access_mode');
|
||||
$oConfig->Set('access_mode', ACCESS_FULL);
|
||||
|
||||
MetaModel::DBCreate(array($this, 'LogQueryCallback'));
|
||||
MetaModel::DBCreate([$this, 'LogQueryCallback']);
|
||||
$this->log_ok("Database structure successfully updated.");
|
||||
|
||||
// Check (and update only if it seems needed) the hierarchical keys
|
||||
@@ -626,15 +558,10 @@ class RunTimeEnvironment
|
||||
|
||||
// Restore the previous access mode
|
||||
$oConfig->Set('access_mode', $iPrevAccessMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strlen($oConfig->Get('db_subname')) > 0)
|
||||
{
|
||||
} else {
|
||||
if (strlen($oConfig->Get('db_subname')) > 0) {
|
||||
throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->Get('db_name')."' (prefix: '".$oConfig->Get('db_subname')."'). Please, try selecting another database instance.");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->Get('db_name')."'. Please, try selecting another database instance.");
|
||||
}
|
||||
}
|
||||
@@ -651,46 +578,36 @@ class RunTimeEnvironment
|
||||
|
||||
// Constant classes (e.g. User profiles)
|
||||
//
|
||||
foreach (MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
$aPredefinedObjects = call_user_func(array(
|
||||
foreach (MetaModel::GetClasses() as $sClass) {
|
||||
$aPredefinedObjects = call_user_func([
|
||||
$sClass,
|
||||
'GetPredefinedObjects'
|
||||
));
|
||||
if ($aPredefinedObjects != null)
|
||||
{
|
||||
$this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements.");
|
||||
'GetPredefinedObjects',
|
||||
]);
|
||||
if ($aPredefinedObjects != null) {
|
||||
$this->log_info("$sClass::GetPredefinedObjects() returned ".count($aPredefinedObjects)." elements.");
|
||||
|
||||
// Create/Delete/Update objects of this class,
|
||||
// according to the given constant values
|
||||
//
|
||||
$aDBIds = array();
|
||||
$aDBIds = [];
|
||||
$oAll = new DBObjectSet(new DBObjectSearch($sClass));
|
||||
while ($oObj = $oAll->Fetch())
|
||||
{
|
||||
if (array_key_exists($oObj->GetKey(), $aPredefinedObjects))
|
||||
{
|
||||
while ($oObj = $oAll->Fetch()) {
|
||||
if (array_key_exists($oObj->GetKey(), $aPredefinedObjects)) {
|
||||
$aObjValues = $aPredefinedObjects[$oObj->GetKey()];
|
||||
foreach ($aObjValues as $sAttCode => $value)
|
||||
{
|
||||
foreach ($aObjValues as $sAttCode => $value) {
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
$oObj->DBUpdate();
|
||||
$aDBIds[$oObj->GetKey()] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oObj->DBDelete();
|
||||
}
|
||||
}
|
||||
foreach ($aPredefinedObjects as $iRefId => $aObjValues)
|
||||
{
|
||||
if (! array_key_exists($iRefId, $aDBIds))
|
||||
{
|
||||
foreach ($aPredefinedObjects as $iRefId => $aObjValues) {
|
||||
if (! array_key_exists($iRefId, $aDBIds)) {
|
||||
$oNewObj = MetaModel::NewObject($sClass);
|
||||
$oNewObj->SetKey($iRefId);
|
||||
foreach ($aObjValues as $sAttCode => $value)
|
||||
{
|
||||
foreach ($aObjValues as $sAttCode => $value) {
|
||||
$oNewObj->Set($sAttCode, $value);
|
||||
}
|
||||
$oNewObj->DBInsert();
|
||||
@@ -710,22 +627,20 @@ class RunTimeEnvironment
|
||||
MetaModel::GetConfig()->Set('access_mode', ACCESS_FULL);
|
||||
//$oConfig->Set('access_mode', ACCESS_FULL);
|
||||
|
||||
if (CMDBSource::DBName() == '')
|
||||
{
|
||||
if (CMDBSource::DBName() == '') {
|
||||
// In case this has not yet been done
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
}
|
||||
|
||||
if ($sShortComment === null)
|
||||
{
|
||||
if ($sShortComment === null) {
|
||||
$sShortComment = 'Done by the setup program';
|
||||
}
|
||||
$sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE;
|
||||
|
||||
// Record datamodel version
|
||||
$aData = array(
|
||||
$aData = [
|
||||
'source_dir' => $oConfig->Get('source_dir'),
|
||||
);
|
||||
];
|
||||
$iInstallationTime = time(); // Make sure that all modules record the same installation time
|
||||
$oInstallRec = new ModuleInstallation();
|
||||
$oInstallRec->Set('name', DATAMODEL_MODULE);
|
||||
@@ -744,10 +659,9 @@ class RunTimeEnvironment
|
||||
$oInstallRec->Set('installed', $iInstallationTime);
|
||||
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
|
||||
|
||||
|
||||
// Record installed modules and extensions
|
||||
//
|
||||
$aAvailableExtensions = array();
|
||||
$aAvailableExtensions = [];
|
||||
$aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir());
|
||||
foreach ($aSelectedModuleCodes as $sModuleId) {
|
||||
if (!array_key_exists($sModuleId, $aAvailableModules)) {
|
||||
@@ -757,7 +671,7 @@ class RunTimeEnvironment
|
||||
$sName = $sModuleId;
|
||||
$sVersion = $aModuleData['version_code'];
|
||||
$sUninstallable = $aModuleData['uninstallable'] ?? 'yes';
|
||||
$aComments = array();
|
||||
$aComments = [];
|
||||
$aComments[] = $sShortComment;
|
||||
if ($aModuleData['mandatory']) {
|
||||
$aComments[] = 'Mandatory';
|
||||
@@ -788,27 +702,24 @@ class RunTimeEnvironment
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
}
|
||||
|
||||
if ($this->oExtensionsMap)
|
||||
{
|
||||
if ($this->oExtensionsMap) {
|
||||
// Mark as chosen the selected extensions code passed to us
|
||||
// Note: some other extensions may already be marked as chosen
|
||||
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
|
||||
{
|
||||
if (in_array($oExtension->sCode, $aSelectedExtensionCodes))
|
||||
{
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) {
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->oExtensionsMap->GetChoices() as $oExtension)
|
||||
{
|
||||
foreach ($this->oExtensionsMap->GetChoices() as $oExtension) {
|
||||
$oInstallRec = new ExtensionInstallation();
|
||||
$oInstallRec->Set('code', $oExtension->sCode);
|
||||
$oInstallRec->Set('label', $oExtension->sLabel);
|
||||
$oInstallRec->Set('version', $oExtension->sVersion);
|
||||
$oInstallRec->Set('source', $oExtension->sSource);
|
||||
$oInstallRec->Set('source', $oExtension->sSource);
|
||||
$oInstallRec->Set('uninstallable', $oExtension->CanBeUninstalled() ? 'yes' : 'no');
|
||||
$oInstallRec->Set('installed', $iInstallationTime);
|
||||
$oInstallRec->Set('description', $oExtension->sDescription);
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
@@ -827,14 +738,11 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function GetApplicationVersion(Config $oConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$sSQLQuery = "SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install";
|
||||
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
} 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());
|
||||
@@ -843,37 +751,29 @@ class RunTimeEnvironment
|
||||
|
||||
$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)
|
||||
{
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '')
|
||||
{
|
||||
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)
|
||||
{
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
if ($aInstall['name'] == DATAMODEL_MODULE) {
|
||||
$aResult['datamodel_version'] = $sModuleVersion;
|
||||
$aComments = json_decode($aInstall['comment'], true);
|
||||
if (is_array($aComments))
|
||||
{
|
||||
if (is_array($aComments)) {
|
||||
$aResult = array_merge($aResult, $aComments);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aResult['product_name'] = $aInstall['name'];
|
||||
$aResult['product_version'] = $sModuleVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('datamodel_version', $aResult))
|
||||
{
|
||||
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'];
|
||||
@@ -884,10 +784,8 @@ class RunTimeEnvironment
|
||||
|
||||
public static function MakeDirSafe($sDir)
|
||||
{
|
||||
if (!is_dir($sDir))
|
||||
{
|
||||
if (!@mkdir($sDir))
|
||||
{
|
||||
if (!is_dir($sDir)) {
|
||||
if (!@mkdir($sDir)) {
|
||||
throw new Exception("Failed to create directory '$sDir', please check that the web server process has enough rights to create the directory.");
|
||||
}
|
||||
@chmod($sDir, 0770); // RWX for owner and group, nothing for others
|
||||
@@ -926,8 +824,7 @@ class RunTimeEnvironment
|
||||
{
|
||||
$sSetupQueriesFilePath = SetupUtils::GetSetupQueriesFilePath();
|
||||
$hSetupQueriesFile = @fopen($sSetupQueriesFilePath, 'a');
|
||||
if ($hSetupQueriesFile !== false)
|
||||
{
|
||||
if ($hSetupQueriesFile !== false) {
|
||||
fwrite($hSetupQueriesFile, "$sQuery\n");
|
||||
fclose($hSetupQueriesFile);
|
||||
}
|
||||
@@ -936,10 +833,9 @@ class RunTimeEnvironment
|
||||
public function GetCurrentDataModelVersion()
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'");
|
||||
$oSet = new DBObjectSet($oSearch, array('installed' => false));
|
||||
$oSet = new DBObjectSet($oSearch, ['installed' => false]);
|
||||
$oLatestDM = $oSet->Fetch();
|
||||
if ($oLatestDM == null)
|
||||
{
|
||||
if ($oLatestDM == null) {
|
||||
return '0.0.0';
|
||||
}
|
||||
return $oLatestDM->Get('version');
|
||||
@@ -947,12 +843,9 @@ class RunTimeEnvironment
|
||||
|
||||
public function Commit()
|
||||
{
|
||||
if ($this->sFinalEnv != $this->sTargetEnv)
|
||||
{
|
||||
if (file_exists(utils::GetDataPath().$this->sTargetEnv.'.delta.xml'))
|
||||
{
|
||||
if (file_exists(utils::GetDataPath().$this->sFinalEnv.'.delta.xml'))
|
||||
{
|
||||
if ($this->sFinalEnv != $this->sTargetEnv) {
|
||||
if (file_exists(utils::GetDataPath().$this->sTargetEnv.'.delta.xml')) {
|
||||
if (file_exists(utils::GetDataPath().$this->sFinalEnv.'.delta.xml')) {
|
||||
// Make a "previous" file
|
||||
copy(
|
||||
utils::GetDataPath().$this->sFinalEnv.'.delta.xml',
|
||||
@@ -1013,34 +906,24 @@ class RunTimeEnvironment
|
||||
*/
|
||||
protected function CommitFile($sSource, $sDest, $bSourceMustExist = true)
|
||||
{
|
||||
if (file_exists($sSource))
|
||||
{
|
||||
if (file_exists($sSource)) {
|
||||
SetupUtils::builddir(dirname($sDest));
|
||||
if (file_exists($sDest))
|
||||
{
|
||||
if (file_exists($sDest)) {
|
||||
$bRes = @unlink($sDest);
|
||||
if (!$bRes)
|
||||
{
|
||||
if (!$bRes) {
|
||||
throw new Exception('Commit - Failed to cleanup destination file: '.$sDest);
|
||||
}
|
||||
}
|
||||
rename($sSource, $sDest);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// The file does not exist
|
||||
if ($bSourceMustExist)
|
||||
{
|
||||
if ($bSourceMustExist) {
|
||||
throw new Exception('Commit - Missing file: '.$sSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Align the destination with the source... make sure there is NO file
|
||||
if (file_exists($sDest))
|
||||
{
|
||||
if (file_exists($sDest)) {
|
||||
$bRes = @unlink($sDest);
|
||||
if (!$bRes)
|
||||
{
|
||||
if (!$bRes) {
|
||||
throw new Exception('Commit - Failed to cleanup destination file: '.$sDest);
|
||||
}
|
||||
}
|
||||
@@ -1059,22 +942,15 @@ class RunTimeEnvironment
|
||||
*/
|
||||
protected function CommitDir($sSource, $sDest, $bSourceMustExist = true, $bRemoveSource = true)
|
||||
{
|
||||
if (file_exists($sSource))
|
||||
{
|
||||
if (file_exists($sSource)) {
|
||||
SetupUtils::movedir($sSource, $sDest, $bRemoveSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// The file does not exist
|
||||
if ($bSourceMustExist)
|
||||
{
|
||||
if ($bSourceMustExist) {
|
||||
throw new Exception('Commit - Missing directory: '.$sSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Align the destination with the source... make sure there is NO file
|
||||
if (file_exists($sDest))
|
||||
{
|
||||
if (file_exists($sDest)) {
|
||||
SetupUtils::rrmdir($sDest);
|
||||
}
|
||||
}
|
||||
@@ -1083,8 +959,7 @@ class RunTimeEnvironment
|
||||
|
||||
public function Rollback()
|
||||
{
|
||||
if ($this->sFinalEnv != $this->sTargetEnv)
|
||||
{
|
||||
if ($this->sFinalEnv != $this->sTargetEnv) {
|
||||
SetupUtils::tidydir(APPROOT.'env-'.$this->sTargetEnv);
|
||||
}
|
||||
}
|
||||
@@ -1098,10 +973,8 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function CallInstallerHandlers($aAvailableModules, $aSelectedModules, $sHandlerName)
|
||||
{
|
||||
foreach($aAvailableModules as $sModuleId => $aModule)
|
||||
{
|
||||
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules))
|
||||
{
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules)) {
|
||||
$aArgs = [MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code']];
|
||||
RunTimeEnvironment::CallInstallerHandler($aAvailableModules[$sModuleId], $sHandlerName, $aArgs);
|
||||
}
|
||||
@@ -1120,14 +993,13 @@ class RunTimeEnvironment
|
||||
public static function CallInstallerHandler(array $aModuleInfo, $sHandlerName, array $aArgs)
|
||||
{
|
||||
$sModuleInstallerClass = ModuleFileReader::GetInstance()->GetAndCheckModuleInstallerClass($aModuleInfo);
|
||||
if (is_null($sModuleInstallerClass)){
|
||||
if (is_null($sModuleInstallerClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupLog::Info("Calling Module Handler: $sModuleInstallerClass::$sHandlerName", null, $aArgs);
|
||||
SetupLog::Debug("Calling Module Handler: $sModuleInstallerClass::$sHandlerName");
|
||||
$aCallSpec = [$sModuleInstallerClass, $sHandlerName];
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
if (is_callable($aCallSpec)) {
|
||||
try {
|
||||
call_user_func_array($aCallSpec, $aArgs);
|
||||
} catch (Exception $e) {
|
||||
@@ -1160,8 +1032,8 @@ class RunTimeEnvironment
|
||||
SetupLog::Info("starting data load session");
|
||||
$oDataLoader->StartSession($oMyChange);
|
||||
|
||||
$aFiles = array();
|
||||
$aPreviouslyLoadedFiles = array();
|
||||
$aFiles = [];
|
||||
$aPreviouslyLoadedFiles = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId != ROOT_MODULE)) {
|
||||
$sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']);
|
||||
@@ -1172,22 +1044,15 @@ class RunTimeEnvironment
|
||||
if ($bSampleData) {
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Load only structural data
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($bSampleData)
|
||||
{
|
||||
} else {
|
||||
if ($bSampleData) {
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Load only structural data
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
@@ -1199,12 +1064,10 @@ class RunTimeEnvironment
|
||||
// Simulate the load of the previously loaded files, in order to initialize
|
||||
// the mapping between the identifiers in the XML and the actual identifiers
|
||||
// in the current database
|
||||
foreach($aPreviouslyLoadedFiles as $sFileRelativePath)
|
||||
{
|
||||
foreach ($aPreviouslyLoadedFiles as $sFileRelativePath) {
|
||||
$sFileName = APPROOT.$sFileRelativePath;
|
||||
SetupLog::Info("Loading file: $sFileName (just to get the keys mapping)");
|
||||
if (empty($sFileName) || !file_exists($sFileName))
|
||||
{
|
||||
if (empty($sFileName) || !file_exists($sFileName)) {
|
||||
throw(new Exception("File $sFileName does not exist"));
|
||||
}
|
||||
|
||||
@@ -1213,12 +1076,10 @@ class RunTimeEnvironment
|
||||
SetupLog::Info($sResult);
|
||||
}
|
||||
|
||||
foreach($aFiles as $sFileRelativePath)
|
||||
{
|
||||
foreach ($aFiles as $sFileRelativePath) {
|
||||
$sFileName = APPROOT.$sFileRelativePath;
|
||||
SetupLog::Info("Loading file: $sFileName");
|
||||
if (empty($sFileName) || !file_exists($sFileName))
|
||||
{
|
||||
if (empty($sFileName) || !file_exists($sFileName)) {
|
||||
throw(new Exception("File $sFileName does not exist"));
|
||||
}
|
||||
|
||||
@@ -1240,9 +1101,8 @@ class RunTimeEnvironment
|
||||
*/
|
||||
protected static function MergeWithRelativeDir($aSourceArray, $sBaseDir, $aFilesToMerge)
|
||||
{
|
||||
$aToMerge = array();
|
||||
foreach($aFilesToMerge as $sFile)
|
||||
{
|
||||
$aToMerge = [];
|
||||
foreach ($aFilesToMerge as $sFile) {
|
||||
$aToMerge[] = $sBaseDir.'/'.$sFile;
|
||||
}
|
||||
return array_merge($aSourceArray, $aToMerge);
|
||||
@@ -1258,10 +1118,8 @@ class RunTimeEnvironment
|
||||
{
|
||||
$iCount = 0;
|
||||
$fStart = microtime(true);
|
||||
foreach(MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass))
|
||||
{
|
||||
foreach (MetaModel::GetClasses() as $sClass) {
|
||||
if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass)) {
|
||||
//if a class is not persisted and is abstract, the code below would crash. Needed by the class AbstractRessource. This is tolerable to skip this because we check the setup process integrity, not the datamodel integrity.
|
||||
continue;
|
||||
}
|
||||
@@ -1270,24 +1128,21 @@ class RunTimeEnvironment
|
||||
$oSearch->SetShowObsoleteData(false);
|
||||
$oSQLQuery = $oSearch->GetSQLQueryStructure(null, false);
|
||||
$sViewName = MetaModel::DBGetView($sClass);
|
||||
if (strlen($sViewName) > 64)
|
||||
{
|
||||
if (strlen($sViewName) > 64) {
|
||||
throw new Exception("Class name too long for class: '$sClass'. The name of the corresponding view ($sViewName) would exceed MySQL's limit for the name of a table (64 characters).");
|
||||
}
|
||||
$sTableName = MetaModel::DBGetTable($sClass);
|
||||
if (strlen($sTableName) > 64)
|
||||
{
|
||||
if (strlen($sTableName) > 64) {
|
||||
throw new Exception("Table name too long for class: '$sClass'. The name of the corresponding MySQL table ($sTableName) would exceed MySQL's limit for the name of a table (64 characters).");
|
||||
}
|
||||
$iTableCount = $oSQLQuery->CountTables();
|
||||
if ($iTableCount > 61)
|
||||
{
|
||||
if ($iTableCount > 61) {
|
||||
throw new Exception("Class requiring too many tables: '$sClass'. The structure of the class ($sClass) would require a query with more than 61 JOINS (MySQL's limitation).");
|
||||
}
|
||||
$iCount++;
|
||||
}
|
||||
$fDuration = microtime(true) - $fStart;
|
||||
|
||||
return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0);
|
||||
return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration * 1000.0);
|
||||
}
|
||||
} // End of class
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1310,14 +1310,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 +1327,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 +1345,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1452,7 +1455,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>');
|
||||
|
||||
@@ -1846,6 +1849,7 @@ EOF
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
|
||||
protected function GetStepInfo($idx = null)
|
||||
{
|
||||
$aStepInfo = null;
|
||||
@@ -1866,12 +1870,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 +1886,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>',
|
||||
@@ -1958,18 +1965,41 @@ EOF
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$bIsDefault = array_key_exists($sChoiceId, $aDefaults);
|
||||
|
||||
$bCanBeUninstalled = $this->oExtensionsMap->Get($aChoice['extension_code'])->CanBeUninstalled();
|
||||
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
|
||||
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] : $oITopExtension->CanBeUninstalled();
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $this->bUpgrade && $bIsDefault && !$bCanBeUninstalled && !$bDisableUninstallCheck;
|
||||
;
|
||||
$bDisabled = $bMandatory || $bAllDisabled;
|
||||
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
|
||||
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
|
||||
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
|
||||
$bChecked = $bMandatory || $bSelected;
|
||||
|
||||
$sTooltip = '';
|
||||
$sUnremovable = '';
|
||||
if ($bMissingFromDisk) {
|
||||
$sTooltip .= '<span class="setup-extension-tag removed">source removed</span>';
|
||||
}
|
||||
if ($bInstalled) {
|
||||
$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 (!$bCanBeUninstalled) {
|
||||
$sTooltip .= '<span class="setup-extension-tag notuninstallable">cannot be uninstalled</span>';
|
||||
}
|
||||
if ($bDisabled && !$bChecked && !$bCanBeUninstalled && !$bDisableUninstallCheck) {
|
||||
$this->bCanMoveForward = false;//Disable "Next"
|
||||
}
|
||||
$sChecked = $bChecked ? ' checked ' : '';
|
||||
$sDisabled = $bDisabled ? ' disabled data-disabled="disabled" ' : '';
|
||||
$sUnremovable = !$bCanBeUninstalled ? ' unremovable ' : '';
|
||||
$sMissingModule = $bMissingFromDisk ? 'setup-extension--missing' : '';
|
||||
|
||||
$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);
|
||||
$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, $bDisabled, $sTooltip);
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
$sChoiceName = null;
|
||||
@@ -2030,14 +2060,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 +2081,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';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class DBObjectSetTest extends ItopDataTestCase
|
||||
{
|
||||
public const USE_TRANSACTION = true;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp(); // TODO: Change the autogenerated stub
|
||||
$this->CreateURs();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
CMDBSource::TriggerExceptionWhenSqlQuery(null);
|
||||
parent::tearDown(); // TODO: Change the autogenerated stub
|
||||
}
|
||||
|
||||
private function CreateURs()
|
||||
{
|
||||
$this->CreateTestOrganization();
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$this->CreateTicket($i);
|
||||
}
|
||||
}
|
||||
|
||||
public function testCount()
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT UserRequest");
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
$iCount = $oSet->Count();
|
||||
$oSet->Fetch();
|
||||
$this->assertEquals($iCount, $oSet->Count());
|
||||
$this->assertEquals($iCount, $oSet->CountWithLimit(0));
|
||||
$this->assertTrue($oSet->CountExceeds(0));
|
||||
|
||||
//no DB SQL query: exception will be raised after here
|
||||
CMDBSource::TriggerExceptionWhenSqlQuery(__METHOD__.' :'.__LINE__);
|
||||
$this->assertEquals($iCount, $oSet->Count(), 'should use cache and not call DB again');
|
||||
}
|
||||
|
||||
public function testRewind()
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT UserRequest");
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
while ($oObj = $oSet->Fetch()) {
|
||||
$this->assertNotEquals(0, $oObj->GetKey());
|
||||
}
|
||||
|
||||
//no DB SQL query: exception will be raised after here
|
||||
CMDBSource::TriggerExceptionWhenSqlQuery(__METHOD__.' :'.__LINE__);
|
||||
$oSet->Rewind();
|
||||
while ($oObj = $oSet->Fetch()) {
|
||||
$this->assertNotEquals(0, $oObj->GetKey());
|
||||
}
|
||||
}
|
||||
|
||||
public function testDBObjectSetComparator()
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT UserRequest");
|
||||
$DBObjectSet1 = new DBObjectSet($oSearch);
|
||||
$DBObjectSet3 = new DBObjectSet($oSearch);
|
||||
$oDBObjectSetComparator = new DBObjectSetComparator($DBObjectSet1, $DBObjectSet3);
|
||||
$this->assertTrue($oDBObjectSetComparator->SetsAreEquivalent());
|
||||
}
|
||||
|
||||
public function testDBObjectSetComparator_CheckCache()
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT UserRequest");
|
||||
$DBObjectSet1 = new DBObjectSet($oSearch);
|
||||
$DBObjectSet3 = new DBObjectSet($oSearch);
|
||||
$oDBObjectSetComparator = new DBObjectSetComparator($DBObjectSet1, $DBObjectSet3);
|
||||
$this->assertTrue($oDBObjectSetComparator->SetsAreEquivalent());
|
||||
|
||||
$sMsg = __METHOD__.' :'.__LINE__;
|
||||
//no DB SQL query: exception will be raised after here
|
||||
CMDBSource::TriggerExceptionWhenSqlQuery($sMsg);
|
||||
$oDBObjectSetComparator = new DBObjectSetComparator($DBObjectSet1, $DBObjectSet3);
|
||||
$this->assertTrue($oDBObjectSetComparator->SetsAreEquivalent());
|
||||
|
||||
$oDBObjectSetComparator = new DBObjectSetComparator($DBObjectSet1, new DBObjectSet($oSearch));
|
||||
$this->expectExceptionMessage($sMsg, "should call DB again this time");
|
||||
$this->assertTrue($oDBObjectSetComparator->SetsAreEquivalent());
|
||||
}
|
||||
|
||||
public static function JeffreyProvider()
|
||||
{
|
||||
return [
|
||||
'basic' => [false],
|
||||
'opt trick' => [true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider JeffreyProvider
|
||||
*/
|
||||
public function testJeffrey(bool $bTrick)
|
||||
{
|
||||
echo '<p>-------</p>';
|
||||
|
||||
$this->doesNotPerformAssertions();
|
||||
$iMax = 100;
|
||||
|
||||
|
||||
$oFilter = DBObjectSearch::FromOQL_AllData('SELECT UserRequest');
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
$oSet->OptimizeColumnLoad([
|
||||
'UserRequest' => ['ref', 'status', 'title'],
|
||||
]);
|
||||
|
||||
if ($bTrick) {
|
||||
$oNewSet = DBObjectSet::FromScratch($oSet->GetClass());
|
||||
while ($oObj = $oSet->Fetch()) {
|
||||
$oNewSet->AddObject($oObj);
|
||||
}
|
||||
$oSet = $oNewSet;
|
||||
}
|
||||
|
||||
echo '<p>Start: '.date('Y-m-d H:i:s').'</p>';
|
||||
$i = 0;
|
||||
while ($i < $iMax) {
|
||||
$oSet->Rewind();
|
||||
while ($oObj = $oSet->Fetch()) {
|
||||
// Do nothing
|
||||
$s = $oObj->Get('title');
|
||||
}
|
||||
|
||||
$i += 1;
|
||||
}
|
||||
|
||||
$peak = memory_get_peak_usage(true) / 1000000;
|
||||
echo '<p>End: '.date('Y-m-d H:i:s').'</p><p>Peak memory: '.$peak.'</p> \n';
|
||||
}
|
||||
}
|
||||
@@ -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,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());
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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