mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 15:34:12 +01:00
Compare commits
34 Commits
feature/75
...
faf/module
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18c52f1a71 | ||
|
|
045a9c5658 | ||
|
|
4367bb2868 | ||
|
|
5f1765504c | ||
|
|
bca79a1a06 | ||
|
|
8c30199e9f | ||
|
|
996b98c4c7 | ||
|
|
5dea3c7ce2 | ||
|
|
609215857b | ||
|
|
ae83d677a3 | ||
|
|
8a017b6e95 | ||
|
|
dda99616a5 | ||
|
|
e37322e631 | ||
|
|
64bec2181f | ||
|
|
6ed0f8ef3a | ||
|
|
b194a0b17c | ||
|
|
4207b25bde | ||
|
|
9f2da19da0 | ||
|
|
21c4ffa9e3 | ||
|
|
b31a7fac54 | ||
|
|
46d8591761 | ||
|
|
18a66e6884 | ||
|
|
6c13464011 | ||
|
|
809d1a5f7d | ||
|
|
217e890650 | ||
|
|
d7e02d3859 | ||
|
|
1d7e776f6d | ||
|
|
6319d0a9cb | ||
|
|
5c051a30d3 | ||
|
|
78e13fffb4 | ||
|
|
bb16108f04 | ||
|
|
460a3e356e | ||
|
|
dd68017020 | ||
|
|
0281721638 |
7
datamodels/2.x/itop-oauth-client/module.itop-oauth-client.php
Normal file → Executable file
7
datamodels/2.x/itop-oauth-client/module.itop-oauth-client.php
Normal file → Executable file
@@ -16,6 +16,7 @@ SetupWebPage::AddModule(
|
||||
//
|
||||
'dependencies' => array(
|
||||
'itop-welcome-itil/3.1.0,',
|
||||
'itop-profiles-itil/3.1.0', //SuperUser id 117
|
||||
),
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
@@ -28,7 +29,7 @@ SetupWebPage::AddModule(
|
||||
'src/Service/ApplicationUIExtension.php',
|
||||
),
|
||||
'webservice' => array(
|
||||
|
||||
|
||||
),
|
||||
'data.struct' => array(
|
||||
// add your 'structure' definition XML files here,
|
||||
@@ -36,11 +37,11 @@ SetupWebPage::AddModule(
|
||||
'data.sample' => array(
|
||||
// add your sample data XML files here,
|
||||
),
|
||||
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
|
||||
// Default settings
|
||||
//
|
||||
|
||||
@@ -26,6 +26,7 @@ SetupWebPage::AddModule(
|
||||
'category' => 'Portal',
|
||||
// Setup
|
||||
'dependencies' => array(
|
||||
'itop-attachments/3.2.1', //CMDBChangeOpAttachmentRemoved
|
||||
),
|
||||
'mandatory' => true,
|
||||
'visible' => false,
|
||||
|
||||
@@ -14,6 +14,7 @@ SetupWebPage::AddModule(
|
||||
//
|
||||
'dependencies' => array(
|
||||
'itop-structure/2.7.1',
|
||||
'itop-portal/3.0.0' // module_design_itop_design->module_designs->itop-portal
|
||||
),
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
@@ -29,7 +30,7 @@ SetupWebPage::AddModule(
|
||||
),
|
||||
'data.sample' => array(
|
||||
),
|
||||
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '',
|
||||
|
||||
107
setup/moduledependency/module.class.inc.php
Normal file
107
setup/moduledependency/module.class.inc.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
require_once(__DIR__.'/moduledependency.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;
|
||||
|
||||
public array $aInitialDependencies;
|
||||
public array $aRemainingDependenciesToResolve;
|
||||
|
||||
public function __construct(string $sModuleId)
|
||||
{
|
||||
$this->sModuleId = $sModuleId;
|
||||
list($this->sModuleName, $this->sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (strlen($this->sVersion) == 0) {
|
||||
// No version number found, assume 1.0.0
|
||||
$this->sVersion = '1.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 $aAllDependencies: list of dependencies (string)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetDependencies(array $aAllDependencies): void
|
||||
{
|
||||
$this->aInitialDependencies = $aAllDependencies;
|
||||
$this->aRemainingDependenciesToResolve = [];
|
||||
|
||||
foreach ($aAllDependencies as $sDependencyExpression){
|
||||
$this->aRemainingDependenciesToResolve[$sDependencyExpression]= new ModuleDependency($sDependencyExpression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if module dependencies are resolved with current list of module versions
|
||||
* @param array $aModuleVersions : versions by module names dict
|
||||
* @param array $aSelectedModules : modules names dict
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function IsModuleResolved(array $aModuleVersions, array $aSelectedModules) : bool
|
||||
{
|
||||
$aNextDependencies=[];
|
||||
$bDependenciesSolved = true;
|
||||
foreach($this->aRemainingDependenciesToResolve as $sDepId => $oModuleDependency)
|
||||
{
|
||||
/** @var ModuleDependency $oModuleDependency*/
|
||||
if (!$oModuleDependency->IsDependencyResolved($aModuleVersions, $aSelectedModules))
|
||||
{
|
||||
$aNextDependencies[$sDepId]=$oModuleDependency;
|
||||
$bDependenciesSolved = false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->aRemainingDependenciesToResolve=$aNextDependencies;
|
||||
|
||||
return $bDependenciesSolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array: list of unique module names
|
||||
*/
|
||||
public function GetUnresolvedDependencyModuleNames(): array
|
||||
{
|
||||
$aRes=[];
|
||||
foreach($this->aRemainingDependenciesToResolve as $sDepId => $oModuleDependency) {
|
||||
/** @var ModuleDependency $oModuleDependency */
|
||||
$aRes = array_merge($aRes, $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
return array_unique($aRes);
|
||||
}
|
||||
}
|
||||
142
setup/moduledependency/moduledependency.class.inc.php
Normal file
142
setup/moduledependency/moduledependency.class.inc.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use ModuleFileReaderException;
|
||||
use RunTimeEnvironment;
|
||||
|
||||
/**
|
||||
* Class that handles a module dependency
|
||||
*/
|
||||
class ModuleDependency {
|
||||
private static PhpExpressionEvaluator $oPhpExpressionEvaluator;
|
||||
|
||||
private string $sDependencyExpression;
|
||||
private bool $bInvalidDependencyRegexp=false;
|
||||
private array $aDepencyModuleNames;
|
||||
private array $aParamsPerModuleId;
|
||||
|
||||
public function __construct(string $sDependencyExpression)
|
||||
{
|
||||
$this->sDependencyExpression = $sDependencyExpression;
|
||||
$this->aParamsPerModuleId = [];
|
||||
$this->aDepencyModuleNames = [];
|
||||
|
||||
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 = array();
|
||||
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
|
||||
$sModuleName = $aModuleMatches[1];
|
||||
$this->aDepencyModuleNames[$sModuleName] = true;
|
||||
$sOperator = $aModuleMatches[2];
|
||||
if ($sOperator == '') {
|
||||
$sOperator = '>=';
|
||||
}
|
||||
$sExpectedVersion = $aModuleMatches[3];
|
||||
$this->aParamsPerModuleId[$sModuleId] = [$sModuleName, $sOperator, $sExpectedVersion];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->bInvalidDependencyRegexp=true;
|
||||
}
|
||||
}
|
||||
|
||||
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 GetPotentialPrerequisiteModuleNames() : array
|
||||
{
|
||||
return array_keys($this->aDepencyModuleNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if dependency is resolved with current list of module versions
|
||||
* @param array $aModuleVersions: versions by module names dict
|
||||
* @param array $aSelectedModules: modules names dict
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function IsDependencyResolved(array $aModuleVersions, array $aSelectedModules) : bool
|
||||
{
|
||||
if ($this->bInvalidDependencyRegexp){
|
||||
return false;
|
||||
}
|
||||
|
||||
$aReplacements=[];
|
||||
foreach ($this->aParamsPerModuleId as $sModuleId => list($sModuleName, $sOperator, $sExpectedVersion)){
|
||||
if (array_key_exists($sModuleName, $aModuleVersions))
|
||||
{
|
||||
// module is present, check the version
|
||||
$sCurrentVersion = $aModuleVersions[$sModuleName];
|
||||
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator))
|
||||
{
|
||||
if (array_key_exists($sModuleName, $this->aDepencyModuleNames)) {
|
||||
unset($this->aDepencyModuleNames[$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 present
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->aDepencyModuleNames as $sModuleName => $c)
|
||||
{
|
||||
if (array_key_exists($sModuleName, $aSelectedModules))
|
||||
{
|
||||
// This module is actually a prerequisite
|
||||
if (!array_key_exists($sModuleName, $aModuleVersions))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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/>";
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
public function IsDependencyRegexpInvalid(): bool
|
||||
{
|
||||
return $this->bInvalidDependencyRegexp;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
122
setup/moduledependency/moduledependencysort.class.inc.php
Normal file
122
setup/moduledependency/moduledependencysort.class.inc.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2024 Combodo SAS
|
||||
* This file is part of iTop.
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency\Validation;
|
||||
|
||||
require_once(APPROOT.'setup/modulediscovery.class.inc.php');
|
||||
require_once(APPROOT.'setup/moduledependency/validation/xmlmodulemetainfo.php');
|
||||
require_once(APPROOT.'setup/moduledependency/validation/xmlmodule.php');
|
||||
|
||||
class ModulesDependencyValidationService {
|
||||
private static ?ModulesDependencyValidationService $oInstance;
|
||||
public static array $aModulesDataByModuleName=[];
|
||||
|
||||
public array $aModules=[];
|
||||
private string $sCurrentModule;
|
||||
private array $aDefineNodes;
|
||||
private array $aDependencyNodes;
|
||||
private array $aAllDmFiles=[];
|
||||
private array $aSoftDependencyNodes=[];
|
||||
private array $aSubfoldersByModulename=[];
|
||||
|
||||
public static function GetInstance(): ModulesDependencyValidationService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModulesDependencyValidationService();
|
||||
self::ReadModuleMetaInfo();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
public static function SetInstance(?ModulesDependencyValidationService $instance): void {
|
||||
self::$oInstance = $instance;
|
||||
}
|
||||
|
||||
protected static function ListModuleFiles(string $sDirectory) : array
|
||||
{
|
||||
if (! is_dir($sDirectory)){
|
||||
return [];
|
||||
}
|
||||
|
||||
$aModules=[];
|
||||
if ($hDir = opendir($sDirectory))
|
||||
{
|
||||
// This is the correct way to loop over the directory. (according to the documentation)
|
||||
while (($sFile = readdir($hDir)) !== false)
|
||||
{
|
||||
$aMatches = [];
|
||||
if (is_dir($sDirectory.'/'.$sFile))
|
||||
{
|
||||
if (($sFile != '.') && ($sFile != '..') && ($sFile != '.svn') && ($sFile != 'vendor'))
|
||||
{
|
||||
$aModules = array_merge($aModules, self::ListModuleFiles($sDirectory.'/'.$sFile));
|
||||
}
|
||||
}
|
||||
else if (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches))
|
||||
{
|
||||
$aModules[] = $sDirectory.'/'.$sFile;
|
||||
}
|
||||
}
|
||||
closedir($hDir);
|
||||
}
|
||||
|
||||
return $aModules;
|
||||
}
|
||||
|
||||
public static function ReadModuleMetaInfo(): void
|
||||
{
|
||||
if (count(self::$aModulesDataByModuleName)>0){
|
||||
return;
|
||||
}
|
||||
|
||||
$aDirsToScan = [
|
||||
APPROOT.'datamodels/2.x',
|
||||
APPROOT.'extensions',
|
||||
APPROOT.'data/production-modules',
|
||||
];
|
||||
foreach ($aDirsToScan as $sDir){
|
||||
foreach (self::ListModuleFiles($sDir) as $sFile) {
|
||||
try{
|
||||
$aModuleData = \ModuleFileReader::GetInstance()->ReadModuleFileInformation($sFile);
|
||||
} catch(\ModuleFileReaderException $e){
|
||||
\IssueLog::Error("Cannot read module file", null, [$sFile]);
|
||||
}
|
||||
$sModuleId=$aModuleData[1];
|
||||
|
||||
list($sModuleName, $sVersion) = \ModuleDiscovery::GetModuleName($sModuleId);
|
||||
self::$aModulesDataByModuleName[$sModuleName] = $aModuleData;
|
||||
}
|
||||
|
||||
ksort(self::$aModulesDataByModuleName);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetModuleMetainfo($sModuleName)
|
||||
{
|
||||
return self::$aModulesDataByModuleName[$sModuleName] ?? [];
|
||||
}
|
||||
|
||||
public function ListDatamodelFiles() : array
|
||||
{
|
||||
if (count($this->aAllDmFiles)==0){
|
||||
$aGlobPAtterns = [
|
||||
APPROOT.'datamodels/2.x/*',
|
||||
APPROOT.'application',
|
||||
APPROOT.'core',
|
||||
APPROOT.'extensions',
|
||||
APPROOT.'extensions/*',
|
||||
APPROOT.'data/production-modules',
|
||||
APPROOT.'data/production-modules/*',
|
||||
];
|
||||
|
||||
foreach ($aGlobPAtterns as $sPattern) {
|
||||
$this->aAllDmFiles = array_merge($this->aAllDmFiles, glob("$sPattern/datamodel.*.xml"));
|
||||
}
|
||||
}
|
||||
return $this->aAllDmFiles;
|
||||
}
|
||||
|
||||
private function CreateIfNeededAndGetXmlModule(string $sModuleName) : XmlModule {
|
||||
/** @var XmlModule $oCurrentXmlModule */
|
||||
$oCurrentXmlModule = $this->aModules[$sModuleName] ?? null;
|
||||
if (is_null($oCurrentXmlModule)){
|
||||
$oCurrentXmlModule = new XmlModule($sModuleName);
|
||||
$this->aModules[$sModuleName] = $oCurrentXmlModule;
|
||||
}
|
||||
|
||||
return $oCurrentXmlModule;
|
||||
}
|
||||
|
||||
public function FetchAllDependenciesViaDM()
|
||||
{
|
||||
foreach ($this->ListDatamodelFiles() as $sFile) {
|
||||
$this->FetchXmlMetaInfo($sFile);
|
||||
}
|
||||
|
||||
foreach ($this->aDefineNodes as $sKey => $aModules){
|
||||
foreach ($aModules as $sModuleName){
|
||||
$this->CreateIfNeededAndGetXmlModule($sModuleName);
|
||||
}
|
||||
|
||||
$aSoftlyDependentModules = $this->aSoftDependencyNodes[$sKey] ?? null;
|
||||
if (is_null($aSoftlyDependentModules)){
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($aSoftlyDependentModules as $sModuleName){
|
||||
$oCurrentXmlModule = $this->CreateIfNeededAndGetXmlModule($sModuleName);
|
||||
$oCurrentXmlModule->AddDependency($sKey, $aModules, $this->aModules);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->aDependencyNodes as $sKey => $aModules){
|
||||
foreach ($aModules as $sModuleName){
|
||||
$aDefiningModules = $this->aDefineNodes[$sKey] ?? null;
|
||||
if (is_null($aDefiningModules)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$oCurrentXmlModule = $this->CreateIfNeededAndGetXmlModule($sModuleName);
|
||||
$oCurrentXmlModule->AddDependency($sKey, $aDefiningModules, $this->aModules);
|
||||
}
|
||||
}
|
||||
|
||||
$this->OrderModules();
|
||||
$this->CompleteModuleDependencies();
|
||||
}
|
||||
|
||||
public function FetchAllDependenciesViaModulesFiles()
|
||||
{
|
||||
$aFullnameClassesByModuleName=[];
|
||||
foreach (self::$aModulesDataByModuleName as $sModuleName => $aModuleData){
|
||||
//echo "$sModuleName\n";
|
||||
$aFiles = $aModuleData[2]['datamodel'] ?? [];
|
||||
$sDir = dirname($aModuleData[0]);
|
||||
|
||||
$aDeps=[];
|
||||
foreach ($aFiles as $sFile){
|
||||
if (preg_match("|.*model\.$sModuleName\.php|", $sFile)){
|
||||
continue;
|
||||
}
|
||||
//echo "$sDir/$sFile\n";
|
||||
$aDeps=array_merge($aDeps, $this->ListDeclaredFullnameClassesFromPhpFile("$sDir/$sFile"));
|
||||
}
|
||||
|
||||
$aFullnameClassesByModuleName[$sModuleName]=$aDeps;
|
||||
}
|
||||
|
||||
foreach ($aFullnameClassesByModuleName as $sModuleName => $aFullnameClasses){
|
||||
foreach (self::$aModulesDataByModuleName as $sModuleName2 => $aModuleData){
|
||||
if ($sModuleName2 === $sModuleName){
|
||||
continue;
|
||||
}
|
||||
|
||||
$sDir = dirname($aModuleData[0]);
|
||||
|
||||
if (count($aFullnameClassesByModuleName)==0){
|
||||
continue;
|
||||
}
|
||||
|
||||
$sStr = "";
|
||||
foreach ($aFullnameClasses as $sClass){
|
||||
$sStr .= <<<TXT
|
||||
-e "$sClass"
|
||||
TXT;
|
||||
}
|
||||
|
||||
if (strlen($sStr)==0){
|
||||
continue;
|
||||
}
|
||||
|
||||
$bFound=false;
|
||||
foreach ($this->GetFolders($sModuleName2, $sDir) as $sFolderDir => $sGrepRecursiveCliOption) {
|
||||
$sCliCmd = str_replace('\\', '\\\\\\\\', sprintf("grep -l%s %s $sFolderDir", $sGrepRecursiveCliOption, $sStr));
|
||||
$sOutput = exec($sCliCmd);
|
||||
|
||||
if (strlen($sOutput) != 0) {
|
||||
$bFound=true;
|
||||
$sCliCmd = str_replace('\\', '\\\\\\\\', sprintf("grep -o%s %s $sFolderDir", $sGrepRecursiveCliOption, $sStr));
|
||||
$sOutput = exec($sCliCmd);
|
||||
//echo "|$sOutput|\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bFound){
|
||||
$oCurrentXmlModule = $this->CreateIfNeededAndGetXmlModule($sModuleName);
|
||||
$oCurrentXmlModule2 = $this->CreateIfNeededAndGetXmlModule($sModuleName2);
|
||||
try{
|
||||
$sKey = $this->GetFirstFoundDepsUID($sOutput);
|
||||
} catch(\Exception $e){
|
||||
var_dump($aFullnameClassesByModuleName);
|
||||
var_dump(
|
||||
[ $sStr, $sCliCmd, $sModuleName, $sModuleName2, $sOutput]
|
||||
);
|
||||
throw $e;
|
||||
}
|
||||
$oCurrentXmlModule2->AddDependency($sKey, [$sModuleName], $this->aModules);
|
||||
//echo "$sModuleName2 => $sModuleName\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function GetFirstFoundDepsUID(string $sOutput) : string {
|
||||
if (preg_match_all('|.*:(.*)|m', $sOutput, $aMatches)){
|
||||
//var_dump($aMatches);
|
||||
return $aMatches[1][0];
|
||||
}
|
||||
|
||||
throw new \Exception('no match: ' . $sOutput);
|
||||
}
|
||||
|
||||
private function GetFolders($sModuleName2, $sDir) : array
|
||||
{
|
||||
if (array_key_exists($sModuleName2, $this->aSubfoldersByModulename)){
|
||||
return $this->aSubfoldersByModulename[$sModuleName2];
|
||||
}
|
||||
|
||||
$aRes=[];
|
||||
$aFiles=[];
|
||||
foreach (glob("$sDir/*") as $sPath){
|
||||
if (! is_dir($sPath)){
|
||||
$aFiles[]=$sPath;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($sPath, '\.git') !== false){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($sPath, 'vendor') !== false){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($sPath, 'test') !== false){
|
||||
continue;
|
||||
}
|
||||
|
||||
$aRes[$sPath]="r";
|
||||
}
|
||||
|
||||
$aRes=[ implode(' ', $aFiles) => "" ];
|
||||
$this->aSubfoldersByModulename[$sModuleName2]=$aRes;
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
private function GetModuleSuffix($sFile) : string
|
||||
{
|
||||
if (! preg_match('|.*datamodel\.([^\.]+)\.xml|', $sFile, $aMatches)){
|
||||
throw new \Exception("Regexp issue: $sFile");
|
||||
}
|
||||
return $aMatches[1];
|
||||
}
|
||||
|
||||
public function FetchXmlMetaInfo($sFile) : void {
|
||||
$oDomDoc = new \DOMDocument('1.0', 'UTF-8');
|
||||
libxml_clear_errors();
|
||||
$oDomDoc->loadXml(file_get_contents($sFile));
|
||||
$aErrors = libxml_get_errors();
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
throw new \Exception("Malformed XML");
|
||||
}
|
||||
|
||||
$this->sCurrentModule = $this->GetModuleSuffix($sFile);
|
||||
|
||||
if (! isset($this->aDefineNodes)){
|
||||
$this->aDefineNodes=[];
|
||||
}
|
||||
|
||||
if (! isset($this->aDependencyNodes)){
|
||||
$this->aDependencyNodes=[];
|
||||
}
|
||||
|
||||
foreach ($oDomDoc->childNodes as $oDomNode){
|
||||
$this->FetchMetaInfo($oDomDoc->childNodes);
|
||||
}
|
||||
}
|
||||
|
||||
private function FetchMetaInfo(\DOMNodeList $oDomNodeList, ?string $sPath=null)
|
||||
{
|
||||
/** @var \DOMNode $oDomNode */
|
||||
foreach ($oDomNodeList as $oDomNode) {
|
||||
/** @var \DOMAttr $oDelta */
|
||||
$oDelta = $oDomNode->attributes['_delta'] ?? null;
|
||||
/** @var \DOMAttr $oId */
|
||||
$oId = $oDomNode->attributes['id'] ?? null;
|
||||
|
||||
if (! is_null($oId)) {
|
||||
$sId = $oId->nodeValue;
|
||||
$sCurrentPath = $sPath ? $sPath."->".$sId : $sId;
|
||||
|
||||
if (!is_null($oDelta)) {
|
||||
$oXmlModuleMetaInfo = new XmlModuleMetaInfo($sId, $oDomNode->nodeName, $sCurrentPath, $oDelta->nodeValue);
|
||||
$sKey = $oXmlModuleMetaInfo->GetUID();
|
||||
if ($oXmlModuleMetaInfo->IsDefine()) {
|
||||
if (array_key_exists($sKey, $this->aDefineNodes)) {
|
||||
$this->aDefineNodes[$sKey][] = $this->sCurrentModule;
|
||||
} else {
|
||||
$this->aDefineNodes[$sKey] = [$this->sCurrentModule];
|
||||
}
|
||||
} else {
|
||||
if (array_key_exists($sKey, $this->aDependencyNodes)) {
|
||||
$this->aDependencyNodes[$sKey][] = $this->sCurrentModule;
|
||||
} else {
|
||||
$this->aDependencyNodes[$sKey] = [$this->sCurrentModule];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$oXmlModuleMetaInfo = new XmlModuleMetaInfo($sId, $oDomNode->nodeName, $sCurrentPath, "nodelta");
|
||||
$sKey = $oXmlModuleMetaInfo->GetUID();
|
||||
if (array_key_exists($sKey, $this->aSoftDependencyNodes)) {
|
||||
$this->aSoftDependencyNodes[$sKey][] = $this->sCurrentModule;
|
||||
} else {
|
||||
$this->aSoftDependencyNodes[$sKey] = [$this->sCurrentModule];
|
||||
}
|
||||
}
|
||||
} else if ($oDomNode instanceof \DOMElement){
|
||||
$sCurrentPath = $sPath ? $sPath . '->' . $oDomNode->nodeName : $oDomNode->nodeName;
|
||||
} else{
|
||||
$sCurrentPath = $sPath;
|
||||
}
|
||||
|
||||
$this->FetchMetaInfo($oDomNode->childNodes, $sCurrentPath);
|
||||
}
|
||||
}
|
||||
|
||||
private function OrderModules()
|
||||
{
|
||||
$aModuleDepsCount = [];
|
||||
/** @var XmlModule $oXmlModule */
|
||||
foreach ($this->aModules as $oXmlModule) {
|
||||
$aModuleDepsCount[$oXmlModule->sModuleName] = count($oXmlModule->GetExpandedModuleNames($this->aModules));
|
||||
}
|
||||
|
||||
$aOrderModules=[];
|
||||
while (count($aModuleDepsCount)>0) {
|
||||
asort($aModuleDepsCount);
|
||||
|
||||
foreach ($aModuleDepsCount as $sModuleName => $iCount){
|
||||
//echo "=== OK === $sModuleName\n";
|
||||
if ($iCount>0){
|
||||
/*foreach ($aModuleDepsCount as $sStillToProcessModuleName => $c) {
|
||||
/** @var XmlModule $oXmlStillToProcessModule */
|
||||
/*$oXmlStillToProcessModule = $this->aModules[$sStillToProcessModuleName];
|
||||
echo "=== NOK ($c)=== $sStillToProcessModuleName: ".$oXmlStillToProcessModule."\n";
|
||||
}*/
|
||||
throw new \Exception("still deps with $sModuleName: ");
|
||||
}
|
||||
|
||||
unset($aModuleDepsCount[$sModuleName]);
|
||||
$aOrderModules[$sModuleName] = $this->aModules[$sModuleName];
|
||||
break;
|
||||
}
|
||||
|
||||
//echo "$sModuleName\n";
|
||||
foreach ($aModuleDepsCount as $sStillToProcessModuleName => $c){
|
||||
/** @var XmlModule $oXmlStillToProcessModule */
|
||||
$oXmlStillToProcessModule = $this->aModules[$sStillToProcessModuleName];
|
||||
if (in_array($sModuleName, $oXmlStillToProcessModule->GetExpandedModuleNames($this->aModules))){
|
||||
$aModuleDepsCount[$sStillToProcessModuleName] = $c - 1 ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->aModules = $aOrderModules;
|
||||
}
|
||||
|
||||
private function CompleteModuleDependencies()
|
||||
{
|
||||
/** @var XmlModule $oXmlModule */
|
||||
foreach ($this->aModules as $oXmlModule) {
|
||||
$oXmlModule->CompleteModuleDependencies($this->aModules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read declared classes/interfaces in modules.php file (either directly listed files or inside autoload)
|
||||
* @param string : module file path
|
||||
*
|
||||
* @return array: list of fullname classes
|
||||
*/
|
||||
public function ListDeclaredFullnameClassesFromPhpFile(string $sPath) : array
|
||||
{
|
||||
if (false !== strpos($sPath, 'autoload.php')){
|
||||
return $this->ListDeclaredFullnameClassesFromAutoloadFile($sPath);
|
||||
}
|
||||
|
||||
$aRes=[];
|
||||
|
||||
$sContent = file_get_contents($sPath);
|
||||
|
||||
$sNamespace='';
|
||||
if (preg_match('|namespace (.*)[ ]*;|', $sContent, $aMatches)){
|
||||
$sNamespace=trim($aMatches[1]) . '\\';
|
||||
}
|
||||
|
||||
|
||||
if (preg_match_all('|^class ([a-zA-Z]*) |m', $sContent, $aMatches)){
|
||||
foreach($aMatches[1] as $sClass){
|
||||
$aRes[]=$sNamespace.$sClass;
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match_all('|^interface ([a-zA-Z]*)|m', $sContent, $aMatches)){
|
||||
foreach($aMatches[1] as $sInterface){
|
||||
$aRes[]=$sNamespace.$sInterface;
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read declared classes/interfaces autoload file
|
||||
*
|
||||
* @param string : module file path
|
||||
*
|
||||
* @return array: list of fullname classes
|
||||
*/
|
||||
public function ListDeclaredFullnameClassesFromAutoloadFile(string $sPath) : array
|
||||
{
|
||||
$sAutoloadClassMap = dirname($sPath) . "/composer/autoload_classmap.php";
|
||||
if (!is_file($sAutoloadClassMap)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sTempfile = tempnam(sys_get_temp_dir(), 'autoload_');
|
||||
$sContent = file_get_contents($sAutoloadClassMap);
|
||||
$sReplace=<<<TXT
|
||||
\$vendorDir = 'VENDOR';
|
||||
\$baseDir = 'BASEDIR';
|
||||
\$aModuleFiles=
|
||||
TXT;
|
||||
$sContent = preg_replace('|return|', $sReplace, $sContent);
|
||||
file_put_contents($sTempfile, $sContent);
|
||||
require_once $sTempfile;
|
||||
@unlink($sTempfile);
|
||||
|
||||
$aRes=[];
|
||||
foreach ($aModuleFiles as $sClass => $sClassPath){
|
||||
if (strpos($sClass, 'InstalledVersions')){
|
||||
continue;
|
||||
}
|
||||
if (false !== strpos($sClassPath, 'VENDOR')){
|
||||
continue;
|
||||
}
|
||||
$aRes[]=$sClass;
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
109
setup/moduledependency/validation/xmlmodule.php
Normal file
109
setup/moduledependency/validation/xmlmodule.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency\Validation;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependency;
|
||||
|
||||
class XmlModule {
|
||||
public string $sModuleName;
|
||||
public array $aDependencyModulesNames=[];
|
||||
public array $aExpandedDependencyModulesNames=[];
|
||||
public array $aAllDependencyModulesNames=[];
|
||||
public array $aXMlMetaInfosByModuleNames=[];
|
||||
|
||||
public function __construct(string $sModuleName)
|
||||
{
|
||||
$this->sModuleName = $sModuleName;
|
||||
}
|
||||
|
||||
public function AddDependency(string $sXmlMetaInfoUID, array $aDefiningModuleNames, array $aModules)
|
||||
{
|
||||
$aRemainingModules=[];
|
||||
foreach ($aDefiningModuleNames as $sDefiningModuleName) {
|
||||
if ($sDefiningModuleName === $this->sModuleName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sDefiningModuleName === "core" || $sDefiningModuleName === "application") {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aRemainingModules[]=$sDefiningModuleName;
|
||||
}
|
||||
|
||||
if (count($aRemainingModules)==0){
|
||||
return;
|
||||
}
|
||||
|
||||
/*$aLog = ['itop-bridge-datacenter-mgmt-services', 'itop-datacenter-mgmt'];
|
||||
if (in_array($sDefiningModuleName, $aLog) && in_array($this->sModuleName, $aLog)){
|
||||
echo $this->sModuleName . " => $sDefiningModuleName === " . $sXmlMetaInfoUID . "\n";
|
||||
}*/
|
||||
|
||||
$sKey=implode(' || ', $aRemainingModules);
|
||||
if (! array_key_exists($sKey, $this->aXMlMetaInfosByModuleNames)){
|
||||
$this->aXMlMetaInfosByModuleNames[$sKey]=[$sXmlMetaInfoUID];
|
||||
} else {
|
||||
if (! in_array($sXmlMetaInfoUID, $this->aXMlMetaInfosByModuleNames[$sKey])){
|
||||
$this->aXMlMetaInfosByModuleNames[$sKey][]=$sXmlMetaInfoUID;
|
||||
}
|
||||
}
|
||||
|
||||
if (! array_key_exists($sKey, $this->aDependencyModulesNames)){
|
||||
$aCurrentModules=[];
|
||||
foreach ($aRemainingModules as $sDefiningModuleName) {
|
||||
/** @var XmlModule $oXmlModule */
|
||||
$oXmlModule = $aModules[$sDefiningModuleName];
|
||||
$aCurrentModules[]=$oXmlModule;
|
||||
}
|
||||
$this->aDependencyModulesNames[$sKey]=$aCurrentModules;
|
||||
}
|
||||
}
|
||||
|
||||
public function Depends(string $sModuleName) : bool
|
||||
{
|
||||
return array_key_exists($sModuleName, $this->aDependencyModulesNames) || array_key_exists($sModuleName, $this->aAllDependencyModulesNames);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf("%s (%s)", $this->sModuleName, implode(' & ', array_keys($this->aDependencyModulesNames)));
|
||||
}
|
||||
|
||||
public function CompleteModuleDependencies(array $aAllModules) : void
|
||||
{
|
||||
foreach ($this->aDependencyModulesNames as $sDirectDependency => $oXmlModules){
|
||||
/** @var XmlModule $oDirectDepXmlModule */
|
||||
$oDirectDepXmlModule = $aAllModules[$sDirectDependency] ?? null;
|
||||
if (! is_null($oDirectDepXmlModule)) {
|
||||
foreach ($oDirectDepXmlModule->aDependencyModulesNames as $sDirectDependency2 => $oXmlModules2) {
|
||||
if (!array_key_exists($sDirectDependency2, $this->aDependencyModulesNames) && !in_array($sDirectDependency2, $this->aAllDependencyModulesNames)) {
|
||||
$this->aAllDependencyModulesNames[] = $sDirectDependency2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool $bExpandedModuleNamesComputed=false;
|
||||
public function GetExpandedModuleNames(array $aModules) : array {
|
||||
if ($this->bExpandedModuleNamesComputed){
|
||||
return $this->aExpandedDependencyModulesNames;
|
||||
}
|
||||
|
||||
$this->bExpandedModuleNamesComputed = true;
|
||||
|
||||
$aRes= [];
|
||||
foreach ($this->aDependencyModulesNames as $sDependency => $oXmlModules){
|
||||
$oiTopCoreModuleDependency = new ModuleDependency($sDependency);
|
||||
foreach ($oiTopCoreModuleDependency->GetPotentialPrerequisiteModuleNames() as $sName){
|
||||
$oDependencyModule = $aModules[$sName] ?? null;
|
||||
$aRes[]=$sName;
|
||||
$aRes = array_merge($aRes, $oDependencyModule->GetExpandedModuleNames($aModules));
|
||||
}
|
||||
}
|
||||
|
||||
$this->aExpandedDependencyModulesNames = array_unique($aRes);
|
||||
return $this->aExpandedDependencyModulesNames;
|
||||
}
|
||||
}
|
||||
29
setup/moduledependency/validation/xmlmodulemetainfo.php
Normal file
29
setup/moduledependency/validation/xmlmodulemetainfo.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency\Validation;
|
||||
|
||||
class XmlModuleMetaInfo {
|
||||
public string $sLastNodeId;
|
||||
public string $sNodeName;
|
||||
public string $sPath;
|
||||
public string $sDelta;
|
||||
|
||||
public function __construct(string $sLastNodeId, string $sNodeName, string $sPath, string $sDelta)
|
||||
{
|
||||
$this->sLastNodeId = $sLastNodeId;
|
||||
$this->sNodeName = $sNodeName;
|
||||
$this->sPath = $sPath;
|
||||
$this->sDelta = $sDelta;
|
||||
}
|
||||
|
||||
public function IsDefine() : bool {
|
||||
return
|
||||
$this->sDelta === 'define_if_not_exists'
|
||||
|| $this->sDelta === 'define';
|
||||
}
|
||||
|
||||
public function GetUID() : string
|
||||
{
|
||||
return $this->sNodeName . '_' . $this->sPath;
|
||||
}
|
||||
}
|
||||
181
setup/modulediscovery.class.inc.php
Normal file → Executable file
181
setup/modulediscovery.class.inc.php
Normal file → Executable file
@@ -19,9 +19,12 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
|
||||
require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php');
|
||||
require_once(__DIR__ . '/moduledependency/moduledependencysort.class.inc.php');
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use \Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
|
||||
class MissingDependencyException extends CoreException
|
||||
{
|
||||
@@ -87,7 +90,7 @@ class ModuleDiscovery
|
||||
// Cache the results and the source directories
|
||||
protected static $m_aSearchDirs = null;
|
||||
protected static $m_aModules = array();
|
||||
protected static $m_aModuleVersionByName = array();
|
||||
public static $m_aModuleVersionByName = array();
|
||||
|
||||
// All the entries below are list of file paths relative to the module directory
|
||||
protected static $m_aFilesList = array('datamodel', 'webservice', 'dictionary', 'data.struct', 'data.sample');
|
||||
@@ -186,7 +189,7 @@ class ModuleDiscovery
|
||||
$sDir = dirname($sFilePath);
|
||||
$aDirs = [
|
||||
$sDir => self::$m_sModulePath,
|
||||
$sDir.'/dictionaries' => self::$m_sModulePath.'/dictionaries'
|
||||
$sDir.'/dictionaries' => self::$m_sModulePath.'/dictionaries',
|
||||
];
|
||||
foreach ($aDirs as $sRootDir => $sPath)
|
||||
{
|
||||
@@ -228,72 +231,74 @@ 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 = [];
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$aSelectedModules = [];
|
||||
foreach($aModules as $sId => $aModule)
|
||||
foreach($aModules as $sModuleId => $aModule)
|
||||
{
|
||||
list($sModuleName, ) = self::GetModuleName($sId);
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad))
|
||||
{
|
||||
$aDependencies[$sId] = $aModule['dependencies'];
|
||||
$oModule->SetDependencies($aModule['dependencies']);
|
||||
$aUnresolvedDependencyModules[$sModuleId]=$oModule;
|
||||
$aSelectedModules[$sModuleName] = true;
|
||||
}
|
||||
}
|
||||
ksort($aDependencies);
|
||||
|
||||
ksort($aUnresolvedDependencyModules);
|
||||
$aOrderedModules = [];
|
||||
$aModuleVersions=[];
|
||||
$iLoopCount = 1;
|
||||
while(($iLoopCount < count($aModules)) && (count($aDependencies) > 0) )
|
||||
while(($iLoopCount < count($aModules)+1) && (count($aUnresolvedDependencyModules) > 0) )
|
||||
{
|
||||
foreach($aDependencies as $sId => $aRemainingDeps)
|
||||
foreach($aUnresolvedDependencyModules as $sModuleId => $oModule)
|
||||
{
|
||||
$bDependenciesSolved = true;
|
||||
foreach($aRemainingDeps as $sDepId)
|
||||
{
|
||||
if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules))
|
||||
{
|
||||
$bDependenciesSolved = false;
|
||||
}
|
||||
}
|
||||
if ($bDependenciesSolved)
|
||||
{
|
||||
$aOrderedModules[] = $sId;
|
||||
unset($aDependencies[$sId]);
|
||||
/** @var Module $oModule */
|
||||
if ($oModule->IsModuleResolved($aModuleVersions, $aSelectedModules)){
|
||||
$aOrderedModules[] = $sModuleId;
|
||||
$aModuleVersions[$oModule->GetModuleName()] = $oModule->GetVersion();
|
||||
unset($aUnresolvedDependencyModules[$sModuleId]);
|
||||
}
|
||||
}
|
||||
|
||||
$iLoopCount++;
|
||||
}
|
||||
if ($bAbortOnMissingDependency && count($aDependencies) > 0)
|
||||
|
||||
if ($bAbortOnMissingDependency && count($aUnresolvedDependencyModules) > 0)
|
||||
{
|
||||
ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
|
||||
$aModulesInfo = [];
|
||||
$aModuleDeps = [];
|
||||
foreach($aDependencies as $sId => $aDeps)
|
||||
foreach($aUnresolvedDependencyModules as $sModuleId => $oModule)
|
||||
{
|
||||
$aModule = $aModules[$sId];
|
||||
$aModule = $aModules[$sModuleId];
|
||||
$aDepsWithIcons = [];
|
||||
foreach($aDeps as $sIndex => $sDepId)
|
||||
foreach($oModule->aInitialDependencies as $sIndex => $sDepId)
|
||||
{
|
||||
if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules))
|
||||
{
|
||||
$aDepsWithIcons[$sIndex] = '✅ ' . $sDepId;
|
||||
} else
|
||||
if (array_key_exists($sDepId, $oModule->aRemainingDependenciesToResolve))
|
||||
{
|
||||
$aDepsWithIcons[$sIndex] = '❌ ' . $sDepId;
|
||||
} else
|
||||
{
|
||||
$aDepsWithIcons[$sIndex] = '✅ ' . $sDepId;
|
||||
}
|
||||
}
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons);
|
||||
$aModulesInfo[$sId] = array('module' => $aModule, 'dependencies' => $aDepsWithIcons);
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sModuleId) depends on: ".implode(' + ', $aDepsWithIcons);
|
||||
$aModulesInfo[$sModuleId] = array('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 = array();
|
||||
$aResult = [];
|
||||
foreach($aOrderedModules as $sId)
|
||||
{
|
||||
$aResult[$sId] = $aModules[$sId];
|
||||
@@ -301,18 +306,6 @@ class ModuleDiscovery
|
||||
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;
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
{
|
||||
if (!isset(static::$oPhpExpressionEvaluator)) {
|
||||
@@ -322,99 +315,6 @@ class ModuleDiscovery
|
||||
return static::$oPhpExpressionEvaluator;
|
||||
}
|
||||
|
||||
protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules)
|
||||
{
|
||||
$bResult = false;
|
||||
$aModuleVersions = array();
|
||||
// Separate the module names from their version for an easier comparison later
|
||||
foreach($aOrderedModules as $sModuleId)
|
||||
{
|
||||
$aMatches = array();
|
||||
if (preg_match('|^([^/]+)/(.*)$|', $sModuleId, $aMatches))
|
||||
{
|
||||
$aModuleVersions[$aMatches[1]] = $aMatches[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
// No version number found, assume 1.0.0
|
||||
$aModuleVersions[$sModuleId] = '1.0.0';
|
||||
}
|
||||
}
|
||||
if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches))
|
||||
{
|
||||
$aReplacements = array();
|
||||
$aPotentialPrerequisites = array();
|
||||
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 = array();
|
||||
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
|
||||
@@ -496,7 +396,6 @@ class ModuleDiscovery
|
||||
*/
|
||||
protected static function ListModuleFiles($sRelDir, $sRootDir)
|
||||
{
|
||||
static $iDummyClassIndex = 0;
|
||||
$sDirectory = $sRootDir.'/'.$sRelDir;
|
||||
|
||||
if ($hDir = opendir($sDirectory))
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2024 Combodo SAS
|
||||
* This file is part of iTop.
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\ModuleDependency\Validation;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependency;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Validation\ModulesDependencyValidationService;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Validation\XmlModule;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class ModulesDependencyValidationServiceTest extends ItopTestCase {
|
||||
private array $aFilesToRemove = [];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/moduledependency/validation/modulesdependencyvalidationservice.php');
|
||||
if (substr(PHP_OS, 0, 3) == 'WIN') {
|
||||
$this->markTestSkipped("run under Linux only");
|
||||
}
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
foreach ($this->aFilesToRemove as $sTmpFile){
|
||||
@unlink($sTmpFile);
|
||||
}
|
||||
ModulesDependencyValidationService::SetInstance(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Module dependency validation: make sure dependencies are correct toward classes/interfaces coming from PHP/Xml datamodel files
|
||||
*/
|
||||
public function testReadModuleFileData()
|
||||
{
|
||||
ModulesDependencyValidationService::GetInstance()->FetchAllDependenciesViaModulesFiles();
|
||||
|
||||
ModulesDependencyValidationService::GetInstance()->FetchAllDependenciesViaDM();
|
||||
|
||||
$aErrors=[];
|
||||
/** @var XmlModule $oXmlModule */
|
||||
foreach (ModulesDependencyValidationService::GetInstance()->aModules as $sModuleName => $oXmlModule) {
|
||||
$aCurrentDeps = ModulesDependencyValidationService::GetInstance()::$aModulesDataByModuleName[$sModuleName][2]['dependencies'] ?? [];
|
||||
$aModuleErrors=[];
|
||||
foreach ($oXmlModule->aDependencyModulesNames as $sDepModuleName => $oXmlModule2){
|
||||
$sXmlUIDs = implode('|', $oXmlModule->aXMlMetaInfosByModuleNames[$sDepModuleName]);
|
||||
$bResolved=false;
|
||||
foreach ($aCurrentDeps as $sDepString){
|
||||
$oModuleDependency = new ModuleDependency($sDepString);
|
||||
|
||||
if (in_array($sDepModuleName, $oModuleDependency->GetPotentialPrerequisiteModuleNames())) {
|
||||
$bResolved=true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (false !== strpos($sDepModuleName, '|')){
|
||||
$aDepModules = explode('|', $sDepModuleName);
|
||||
foreach ($aDepModules as $sDepModule){
|
||||
$sDepModule = trim($sDepModule);
|
||||
if (in_array($sDepModule, $oModuleDependency->GetPotentialPrerequisiteModuleNames())) {
|
||||
$bResolved=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bResolved){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($oModuleDependency->GetPotentialPrerequisiteModuleNames() as $sPotentialDepModuleName){
|
||||
/** @var XmlModule $oXmlModule2 */
|
||||
$oXmlModule2 = ModulesDependencyValidationService::GetInstance()->aModules[$sPotentialDepModuleName]??null;
|
||||
|
||||
if (! is_null($oXmlModule2) && $oXmlModule2->Depends($sDepModuleName)){
|
||||
$bResolved=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bResolved) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $bResolved){
|
||||
$aModuleErrors []= "$sModuleName depends on $sDepModuleName but missing in module dependencies: " . implode(' & ', $aCurrentDeps) . ". ($sXmlUIDs)";
|
||||
}
|
||||
}
|
||||
|
||||
if (count($aModuleErrors)){
|
||||
$aErrors[$sModuleName]=$aModuleErrors;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertEquals(0, count($aErrors), var_export($aErrors, true));
|
||||
|
||||
}
|
||||
|
||||
public function testListDeclaredFullnameClassesFromPhpFile()
|
||||
{
|
||||
$aExpected = [
|
||||
'CMDBChangeOp',
|
||||
'CMDBChangeOpCreate',
|
||||
'CMDBChangeOpDelete',
|
||||
'CMDBChangeOpSetAttribute',
|
||||
'CMDBChangeOpSetAttributeScalar',
|
||||
'CMDBChangeOpSetAttributeTagSet',
|
||||
'CMDBChangeOpSetAttributeURL',
|
||||
'CMDBChangeOpSetAttributeBlob',
|
||||
'CMDBChangeOpSetAttributeOneWayPassword',
|
||||
'CMDBChangeOpSetAttributeEncrypted',
|
||||
'CMDBChangeOpSetAttributeText',
|
||||
'CMDBChangeOpSetAttributeLongText',
|
||||
'CMDBChangeOpSetAttributeHTML',
|
||||
'CMDBChangeOpSetAttributeCaseLog',
|
||||
'CMDBChangeOpPlugin',
|
||||
'CMDBChangeOpSetAttributeLinksAddRemove',
|
||||
'CMDBChangeOpSetAttributeLinksTune',
|
||||
'CMDBChangeOpSetAttributeCustomFields',
|
||||
'iCMDBChangeOp',
|
||||
];
|
||||
$this->assertEquals($aExpected, ModulesDependencyValidationService::GetInstance()->ListDeclaredFullnameClassesFromPhpFile(APPROOT . 'core/cmdbchangeop.class.inc.php'));
|
||||
}
|
||||
|
||||
public function testListDeclaredFullnameClassesFromAutoloadFile()
|
||||
{
|
||||
$aExpected = [
|
||||
'Combodo\iTop\OAuthClient\Controller\AjaxOauthClientController',
|
||||
'Combodo\iTop\OAuthClient\Controller\OAuthClientController',
|
||||
'Combodo\iTop\OAuthClient\Service\ApplicationUIExtension',
|
||||
'Combodo\iTop\OAuthClient\Service\PopupMenuExtension',
|
||||
];
|
||||
$this->assertEquals($aExpected, ModulesDependencyValidationService::GetInstance()->ListDeclaredFullnameClassesFromPhpFile(APPROOT . 'datamodels/2.x/itop-oauth-client/vendor/autoload.php'));
|
||||
}
|
||||
|
||||
public function testListDeclaredFullnameClassesFromAutoloadFile_itopfence()
|
||||
{
|
||||
$sContent=<<<PHP
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
\$vendorDir = dirname(__DIR__);
|
||||
\$baseDir = dirname(\$vendorDir);
|
||||
|
||||
return array(
|
||||
'Attribute' => \$vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
||||
'Combodo\\iTop\\Fence\\Checker\\IpRangeChecker' => \$baseDir . '/src/Checker/IpRangeChecker.php',
|
||||
'Combodo\\iTop\\Fence\\Checker\\LoginFailedListener' => \$baseDir . '/src/Checker/LoginFailedListener.php',
|
||||
);
|
||||
PHP;
|
||||
$sDir = sys_get_temp_dir().'/'.uniqid();
|
||||
mkdir($sDir);
|
||||
$sComposerDir = $sDir.'/composer';
|
||||
mkdir($sComposerDir);
|
||||
$sPath = "$sComposerDir/autoload_classmap.php";
|
||||
file_put_contents($sPath, $sContent);
|
||||
|
||||
$aExpected = [
|
||||
'Combodo\iTop\\Fence\Checker\IpRangeChecker',
|
||||
'Combodo\iTop\\Fence\Checker\LoginFailedListener',
|
||||
];
|
||||
$aRes = ModulesDependencyValidationService::GetInstance()->ListDeclaredFullnameClassesFromAutoloadFile("$sDir/autoload.php");
|
||||
@unlink($sPath);
|
||||
rmdir($sComposerDir);
|
||||
rmdir($sDir);
|
||||
$this->assertEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testReadModuleMetaInfo()
|
||||
{
|
||||
$aExpected = [
|
||||
APPROOT . 'datamodels/2.x/itop-portal-base/module.itop-portal-base.php',
|
||||
'itop-portal-base/3.3.0',
|
||||
[
|
||||
'label' => 'Portal Development Library',
|
||||
'category' => 'Portal',
|
||||
'dependencies' => [ 'itop-attachments/3.2.1' ],
|
||||
'mandatory' => true,
|
||||
'visible' => false,
|
||||
'datamodel' => [ 'portal/vendor/autoload.php' ],
|
||||
'webservice' => [],
|
||||
'dictionary' => [],
|
||||
'data.struct' => [],
|
||||
'data.sample' => [],
|
||||
'doc.manual_setup' => '',
|
||||
'doc.more_information' => '',
|
||||
'settings' => [],
|
||||
'module_file_path' => APPROOT . 'datamodels/2.x/itop-portal-base/module.itop-portal-base.php'
|
||||
],
|
||||
];
|
||||
$this->assertEquals($aExpected, ModulesDependencyValidationService::GetInstance()->GetModuleMetainfo('itop-portal-base'));
|
||||
}
|
||||
|
||||
public function testGetFirstFoundDepsUID() {
|
||||
$sOutput=<<<TXT
|
||||
/var/www/html/Professional-3.2.1-16428/web/datamodels/2.x/authent-token/src/Hook/MyAccountSectionTabContentExtension.php:Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension
|
||||
TXT;
|
||||
|
||||
$this->assertEquals('Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension', ModulesDependencyValidationService::GetInstance()->GetFirstFoundDepsUID($sOutput));
|
||||
|
||||
|
||||
$sOutput=<<<TXT
|
||||
/var/www/html/Professional-3.2.1-16428/web/datamodels/2.x/authent-token/src/Hook/MyAccountSectionTabContentExtension.php:Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension
|
||||
/var/www/html/Professional-3.2.1-16428/web/datamodels/2.x/authent-token/src/Hook/MyAccountSectionTabContentExtension2.php:Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension2
|
||||
/var/www/html/Professional-3.2.1-16428/web/datamodels/2.x/authent-token/src/Hook/MyAccountSectionTabContentExtension3.php:Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension3
|
||||
TXT;
|
||||
|
||||
$this->assertEquals('Combodo\iTop\MyAccount\Hook\iMyAccountTabContentExtension', ModulesDependencyValidationService::GetInstance()->GetFirstFoundDepsUID($sOutput));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<testsuite name="ModuleIntegration">
|
||||
<file>integration-tests/DictionariesConsistencyAfterSetupTest.php</file>
|
||||
<file>integration-tests/DictionariesConsistencyTest.php</file>
|
||||
<file>integration-tests/moduledependency/validation/ModulesDependencyValidationServiceTest.php</file>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
|
||||
285
tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php
Normal file
285
tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php
Normal file
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MissingDependencyException;
|
||||
use ModuleDiscovery;
|
||||
|
||||
class ModuleDiscoveryTest extends ItopDataTestCase
|
||||
{
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_CheckMissingDependenciesAreCorrectlyOrderedInTheException()
|
||||
{
|
||||
$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);
|
||||
|
||||
ModuleDiscovery::OrderModulesByDependencies($aModules, true, null);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_ValidateExceptionWithSomeDependenciesResolved()
|
||||
{
|
||||
$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: ✅ id2/456 + ❌ id4/666,
|
||||
label1 (id: id1/123) depends on: ✅ id2/456 + ❌ id4/666 + ❌ id3/789
|
||||
MSG;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDiscovery::OrderModulesByDependencies($aModules, true, null);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_KeepGoingEvenWithFailure_WithSomeDependenciesResolved()
|
||||
{
|
||||
$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 = ModuleDiscovery::OrderModulesByDependencies($aModules, false, null);
|
||||
|
||||
$aExpected = [
|
||||
'id2/456',
|
||||
];
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_UnResolveWithCircularDependency()
|
||||
{
|
||||
$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);
|
||||
|
||||
ModuleDiscovery::OrderModulesByDependencies($aModules, true, null);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_ResolveOk()
|
||||
{
|
||||
$aModules=[
|
||||
"id0/1" => [
|
||||
'dependencies' => [ 'id2/2 || id1/1'],
|
||||
'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",
|
||||
"id1/1",
|
||||
"id0/1",
|
||||
];
|
||||
|
||||
$aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_ResolveOk2()
|
||||
{
|
||||
$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 = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
|
||||
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 = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_ResolveOk_ModulesToLoadProvided()
|
||||
{
|
||||
$aModules=[
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => ['id3/3 || id3-itil/3'],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label3',
|
||||
],
|
||||
"id3-itil/3" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label3-itil',
|
||||
],
|
||||
];
|
||||
|
||||
foreach(["id3", "id3-itil"] as $sLastModuleNameToLoad) {
|
||||
$aExpected = [
|
||||
"$sLastModuleNameToLoad/3",
|
||||
"id2/2",
|
||||
"id1/1",
|
||||
];
|
||||
|
||||
$aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad]);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependenciesNewComputation_RealExample(){
|
||||
$aModules = json_decode(file_get_contents(__DIR__ . '/ressources/module_deps.json'), true);
|
||||
|
||||
$aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null);
|
||||
|
||||
$aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids.json'), true);
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\Setup\ModuleDependency;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class ModuleDependencySortTest extends ItopDataTestCase
|
||||
{
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->RequireOnceItopFile('setup/moduledependency/moduledependencysort.class.inc.php');
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_NoDependencies(){
|
||||
$aUnresolvedDependencyModules = [];
|
||||
foreach (['c', 'b', 'a'] as $sModuleId){
|
||||
$this->AddModule($aUnresolvedDependencyModules, $sModuleId, []);
|
||||
}
|
||||
ModuleDependencySort::GetInstance()->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', []);
|
||||
|
||||
ModuleDependencySort::GetInstance()->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']);
|
||||
|
||||
ModuleDependencySort::GetInstance()->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', []);
|
||||
|
||||
ModuleDependencySort::GetInstance()->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;
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_RealExample(){
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$aDependencies = json_decode(file_get_contents(__DIR__ . '/ressources/module_deps.json'), true);
|
||||
foreach ($aDependencies as $sModuleId => $aModuleData){
|
||||
$this->AddModule($aUnresolvedDependencyModules, $sModuleId, $aModuleData['dependencies']);
|
||||
}
|
||||
|
||||
ModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
|
||||
$aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids2.json'), true);
|
||||
$this->assertEquals(
|
||||
$aExpected,
|
||||
array_keys($aUnresolvedDependencyModules));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependency;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class ModuleDependencyTest extends ItopDataTestCase
|
||||
{
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/moduledependency/moduledependency.class.inc.php');
|
||||
}
|
||||
|
||||
public function testModuleDependencyInit_Invalid()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('||');
|
||||
$this->assertEquals(true, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
}
|
||||
|
||||
public function testModuleDependencyInit()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.0');
|
||||
$this->assertEquals(['itop-config-mgmt/2.4.0' => [ 'itop-config-mgmt', '>=', '2.4.0']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
$this->assertEquals(['itop-config-mgmt'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
public static function WithOperatorProvider()
|
||||
{
|
||||
$aUsecases=[];
|
||||
foreach (['>', '>=', '<', '<='] as $sOperator){
|
||||
$aUsecases[$sOperator]=[$sOperator];
|
||||
}
|
||||
return $aUsecases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithOperatorProvider
|
||||
*/
|
||||
public function testModuleDependencyInit_WithOperator($sOperator)
|
||||
{
|
||||
$sDepId = "itop-config-mgmt/{$sOperator}2.4.0";
|
||||
$oModuleDependency = new ModuleDependency($sDepId);
|
||||
$this->assertEquals([$sDepId => [ 'itop-config-mgmt', $sOperator, '2.4.0']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
$this->assertEquals(['itop-config-mgmt'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
public static function WithOperatorOperand()
|
||||
{
|
||||
$aUsecases=[];
|
||||
foreach (['&&', '||'] as $sOperand){
|
||||
$aUsecases[$sOperand]=[$sOperand, "itop-structure/3.0.0 $sOperand itop-portal/<3.2.1"];
|
||||
$aUsecases["$sOperand + parenthesis"]=[$sOperand, "(itop-structure/3.0.0 $sOperand itop-portal/<3.2.1)"];
|
||||
}
|
||||
return $aUsecases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithOperatorOperand
|
||||
*/
|
||||
public function testModuleDependencyInit_WithOperand($sOperand, $sDepId)
|
||||
{
|
||||
$sDepId = "itop-structure/3.0.0 $sOperand itop-portal/<3.2.1";
|
||||
$oModuleDependency = new ModuleDependency($sDepId);
|
||||
$this->assertEquals(['itop-structure/3.0.0' => [ 'itop-structure', ">=", '3.0.0'], 'itop-portal/<3.2.1' => [ 'itop-portal', "<", '3.2.1']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
$this->assertEquals(['itop-structure', 'itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_UnresolvedDueToMissingModule()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.0');
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved([], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_UnresolvedDueToWrongModuleVersion()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.0');
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '1.2.3'], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_ResolvedDue_MinorVersion()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.1');
|
||||
$this->assertEquals(true, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '2.4.1-1'], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_ResolvedDue_MinorVersion2()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.1-1');
|
||||
$this->assertEquals(true, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '2.4.1-2'], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_ResolvedDue_MinorVersion3()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.1-1');
|
||||
$this->assertEquals(true, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '2.4.2'], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_UnresolvedDueToWrongModuleVersion_MinorVersion()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.1');
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '2.4.0-1'], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_UnresolvedDueToWrongModuleVersion_MinorVersion2()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.1-1');
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '2.4.1'], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_UnresolvedDueToWrongModuleVersion_MinorVersion3()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.1-1');
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '2.4.1-0'], ['itop-config-mgmt' => true]));
|
||||
}
|
||||
|
||||
public function testModuleIsDependencyResolved_SimpleCase_Resolved()
|
||||
{
|
||||
$oModuleDependency = new ModuleDependency('itop-config-mgmt/2.4.0');
|
||||
$this->assertEquals(['itop-config-mgmt'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
$this->assertEquals(true, $oModuleDependency->IsDependencyResolved(['itop-config-mgmt' => '2.4.1'], ['itop-config-mgmt' => true]));
|
||||
$this->assertEquals([], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
public function testIsDependencyResolved_AndOperand_UnresolvedDueToMissingModule()
|
||||
{
|
||||
$sDepId = "itop-structure/3.0.0 && itop-portal/3.2.1";
|
||||
$oModuleDependency = new ModuleDependency($sDepId);
|
||||
$this->assertEquals(['itop-structure/3.0.0' => [ 'itop-structure', ">=", '3.0.0'], 'itop-portal/3.2.1' => [ 'itop-portal', ">=", '3.2.1']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
$this->assertEquals(['itop-structure', 'itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved(['itop-structure' => '3.0.0'], ['itop-structure' => true, 'itop-portal' => true]));
|
||||
$this->assertEquals(['itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
public function testIsDependencyResolved_AndOperand_UnresolvedDueToWrongModuleVersion()
|
||||
{
|
||||
$sDepId = "itop-structure/3.0.0 && itop-portal/3.2.1";
|
||||
$oModuleDependency = new ModuleDependency($sDepId);
|
||||
$this->assertEquals(['itop-structure/3.0.0' => [ 'itop-structure', ">=", '3.0.0'], 'itop-portal/3.2.1' => [ 'itop-portal', ">=", '3.2.1']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
$this->assertEquals(['itop-structure', 'itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved(['itop-structure' => '3.0.0', 'itop-portal' => '1.0.0'], ['itop-structure' => true, 'itop-portal' => true]));
|
||||
$this->assertEquals(['itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
public function testIsDependencyResolved_AndOperand_Resolved()
|
||||
{
|
||||
$sDepId = "itop-structure/3.0.0 && itop-portal/3.2.1";
|
||||
$oModuleDependency = new ModuleDependency($sDepId);
|
||||
$this->assertEquals(['itop-structure/3.0.0' => [ 'itop-structure', ">=", '3.0.0'], 'itop-portal/3.2.1' => [ 'itop-portal', ">=", '3.2.1']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
$this->assertEquals(['itop-structure', 'itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyResolved(['itop-structure' => '3.0.0'], ['itop-structure' => true]));
|
||||
$this->assertEquals(['itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
|
||||
public function testIsDependencyResolved_OrOperand_ResolvedDueToMissingModule()
|
||||
{
|
||||
$sDepId = "itop-structure/3.0.0 || itop-portal/3.2.1";
|
||||
$oModuleDependency = new ModuleDependency($sDepId);
|
||||
$this->assertEquals(['itop-structure/3.0.0' => [ 'itop-structure', ">=", '3.0.0'], 'itop-portal/3.2.1' => [ 'itop-portal', ">=", '3.2.1']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertEquals(false, $oModuleDependency->IsDependencyRegexpInvalid());
|
||||
$this->assertEquals(['itop-structure', 'itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
|
||||
$this->assertEquals(true, $oModuleDependency->IsDependencyResolved(['itop-structure' => '3.0.0'], ['itop-structure' => true]));
|
||||
$this->assertEquals(['itop-portal'], $oModuleDependency->GetPotentialPrerequisiteModuleNames());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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 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());
|
||||
|
||||
$this->assertEquals(false, $oModule->IsModuleResolved([],[]));
|
||||
$this->assertEquals(['itop-config-mgmt', 'itop-tickets'], $oModule->GetUnresolvedDependencyModuleNames());
|
||||
$this->assertEquals(['itop-config-mgmt/2.7.1', 'itop-tickets/2.7.0'], array_keys($oModule->aRemainingDependenciesToResolve));
|
||||
}
|
||||
|
||||
public function testSetDependencies()
|
||||
{
|
||||
$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',
|
||||
]);
|
||||
$this->assertEquals(['itop-config-mgmt', 'itop-service-mgmt', 'itop-service-mgmt-provider', 'itop-datacenter-mgmt' ],
|
||||
$oModule->GetUnresolvedDependencyModuleNames());
|
||||
|
||||
$this->assertEquals(false, $oModule->IsModuleResolved([],[]));
|
||||
$this->assertEquals(['itop-config-mgmt', 'itop-service-mgmt', 'itop-service-mgmt-provider', 'itop-datacenter-mgmt'],
|
||||
$oModule->GetUnresolvedDependencyModuleNames());
|
||||
$this->assertEquals(['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'],
|
||||
array_keys($oModule->aRemainingDependenciesToResolve));
|
||||
}
|
||||
|
||||
public function testIsResolved_PartialResolution()
|
||||
{
|
||||
$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());
|
||||
|
||||
$this->assertEquals(false, $oModule->IsModuleResolved(['itop-config-mgmt' => '2.7.1'],['itop-config-mgmt'=>true]));
|
||||
$this->assertEquals(['itop-tickets'], $oModule->GetUnresolvedDependencyModuleNames());
|
||||
$this->assertEquals(['itop-tickets/2.7.0'], array_keys($oModule->aRemainingDependenciesToResolve));
|
||||
}
|
||||
|
||||
public function testIsResolved_OK()
|
||||
{
|
||||
$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());
|
||||
|
||||
$this->assertEquals(true, $oModule->IsModuleResolved(['itop-config-mgmt' => '2.7.1', 'itop-tickets' => '2.7.0'],['itop-config-mgmt'=>true, 'itop-tickets' => true]));
|
||||
$this->assertEquals([], $oModule->GetUnresolvedDependencyModuleNames());
|
||||
$this->assertEquals([], array_keys($oModule->aRemainingDependenciesToResolve));
|
||||
}
|
||||
}
|
||||
@@ -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-profiles-itil\/3.2.1","itop-sla-computation\/3.2.1","itop-structure\/3.2.1","itop-welcome-itil\/3.2.1","combodo-db-tools\/3.2.1","itop-config-mgmt\/3.2.1","itop-datacenter-mgmt\/3.2.1","itop-endusers-devices\/3.2.1","itop-hub-connector\/3.2.1","itop-knownerror-mgmt\/3.2.1","itop-oauth-client\/3.2.1","itop-portal\/3.2.1","itop-storage-mgmt\/3.2.1","itop-themes-compat\/3.2.1","itop-tickets\/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-virtualization-mgmt\/3.2.1","itop-bridge-cmdb-ticket\/3.2.1","itop-bridge-virtualization-storage\/3.2.1","itop-change-mgmt-itil\/3.2.1","itop-change-mgmt\/3.2.1","itop-core-update\/3.2.1","itop-faq-light\/3.2.1","itop-bridge-cmdb-services\/3.2.1","itop-incident-mgmt-itil\/3.2.1","itop-full-itil\/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"]
|
||||
@@ -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"]
|
||||
@@ -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-profiles-itil\/3.2.1","itop-sla-computation\/3.2.1","itop-structure\/3.2.1","itop-welcome-itil\/3.2.1","combodo-db-tools\/3.2.1","itop-config-mgmt\/3.2.1","itop-datacenter-mgmt\/3.2.1","itop-endusers-devices\/3.2.1","itop-hub-connector\/3.2.1","itop-knownerror-mgmt\/3.2.1","itop-oauth-client\/3.2.1","itop-portal\/3.2.1","itop-storage-mgmt\/3.2.1","itop-themes-compat\/3.2.1","itop-tickets\/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-virtualization-mgmt\/3.2.1","itop-bridge-cmdb-ticket\/3.2.1","itop-bridge-virtualization-storage\/3.2.1","itop-change-mgmt-itil\/3.2.1","itop-change-mgmt\/3.2.1","itop-core-update\/3.2.1","itop-faq-light\/3.2.1","itop-bridge-cmdb-services\/3.2.1","itop-incident-mgmt-itil\/3.2.1","itop-full-itil\/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"]
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user