diff --git a/setup/module/iTopCoreModuleDependencySort.class.inc.php b/setup/module/iTopCoreModuleDependencySort.class.inc.php index 2ac7cce1e..c65b8a95f 100644 --- a/setup/module/iTopCoreModuleDependencySort.class.inc.php +++ b/setup/module/iTopCoreModuleDependencySort.class.inc.php @@ -121,14 +121,11 @@ class iTopCoreModuleDependencySort { * @param array $aModules The list of modules to process: 'id' => $aModuleInfo * @param bool $bAbortOnMissingDependency ... * @param array $aModulesToLoad List of modules to search for, defaults to all if omitted - * @param int $iLoopCount: used to count loop count for testing purpose (see if algo is optimized) * @return array * @throws \MissingDependencyException */ - public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null, ?int &$iLoopCount=0) + public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null) { - $iLoopCount=0; - // Order the modules to take into account their inter-dependencies $aUnresolvedDependencyModules = []; $aSelectedModules = []; @@ -143,17 +140,13 @@ class iTopCoreModuleDependencySort { $aSelectedModules[$sModuleName] = true; } } - self::SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + + ksort($aUnresolvedDependencyModules); $aOrderedModules = []; $aModuleVersions=[]; - $iPreviousLoopDepencyCount=-1; - $iNextLoopCount=count($aUnresolvedDependencyModules); - while(($iNextLoopCount!=$iPreviousLoopDepencyCount) //stop loop when no new dependency is resolved - && ($iNextLoopCount > 0) //still remaining dependencies - ) + $iLoopCount = 1; + while(($iLoopCount < count($aModules)+1) && (count($aUnresolvedDependencyModules) > 0) ) { - $iLoopCount++; - $iPreviousLoopDepencyCount=$iNextLoopCount; foreach($aUnresolvedDependencyModules as $sModuleId => $oModule) { /** @var iTopCoreModule $oModule */ @@ -164,16 +157,15 @@ class iTopCoreModuleDependencySort { } } - $iNextLoopCount=count($aUnresolvedDependencyModules); - self::SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + $iLoopCount++; } if ($bAbortOnMissingDependency && count($aUnresolvedDependencyModules) > 0) { self::SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); + $aModulesInfo = []; $aModuleDeps = []; - /** @var iTopCoreModule $oModule */ foreach($aUnresolvedDependencyModules as $sModuleId => $oModule) { $aModule = $aModules[$sModuleId]; @@ -196,11 +188,12 @@ class iTopCoreModuleDependencySort { $oException->aModulesInfo = $aModulesInfo; throw $oException; } + // Return the ordered list, so that the dependencies are met... - $aResult = array(); - foreach($aOrderedModules as $sModuleId) + $aResult = []; + foreach($aOrderedModules as $sId) { - $aResult[$sModuleId] = $aModules[$sModuleId]; + $aResult[$sId] = $aModules[$sId]; } return $aResult; } diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php index 66318b862..7aae265a7 100755 --- a/setup/modulediscovery.class.inc.php +++ b/setup/modulediscovery.class.inc.php @@ -97,7 +97,6 @@ class ModuleDiscovery protected static $m_sModulePath = null; private static PhpExpressionEvaluator $oPhpExpressionEvaluator; - private static mixed $bNewFeedback = false; protected static function SetModulePath($sModulePath) { @@ -222,9 +221,6 @@ class ModuleDiscovery return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad); } - public static function UseNewUiFeedback($bNewFeedback){ - self::$bNewFeedback=$bNewFeedback; - } /** * Arrange an list of modules, based on their (inter) dependencies * @param array $aModules The list of modules to process: 'id' => $aModuleInfo @@ -232,7 +228,7 @@ 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 @@ -272,10 +268,6 @@ class ModuleDiscovery } if ($bAbortOnMissingDependency && count($aDependencies) > 0) { - if (self::$bNewFeedback){ - iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, $bAbortOnMissingDependency, $aModulesToLoad); - } - $aModulesInfo = []; $aModuleDeps = []; foreach($aDependencies as $sId => $aDeps) diff --git a/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php b/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php index 2c83e9aa6..7b6faa885 100644 --- a/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/ModuleDiscoveryTest.php @@ -4,6 +4,7 @@ namespace Combodo\iTop\Test\UnitTest\Setup; use Combodo\iTop\Test\UnitTest\ItopDataTestCase; use iTopCoreModuleDependencySort; +use MissingDependencyException; use ModuleDiscovery; class ModuleDiscoveryTest extends ItopDataTestCase @@ -13,21 +14,29 @@ class ModuleDiscoveryTest extends ItopDataTestCase $this->RequireOnceItopFile('setup/modulediscovery.class.inc.php'); } - public function tearDown() : void { - ModuleDiscovery::UseNewUiFeedback(false); - } - public static function OrderModulesByDependenciesProvider(){ + public static function CheckMissingDependenciesAreCorrectlyOrderedInTheExceptionProvider() + { + $sLegacyExpectedMessage = << [0], - 'hybrid computation: order is legacy/ error is new usr friendly one' => [1], - 'new computation' => [2], + 'legacy' => [ 'message' => $sLegacyExpectedMessage, 'is_legacy' => true ], + 'new computation' => [ 'message' => $sExpectedMessage, 'is_legacy' => false ], ]; } /** - * @dataProvider OrderModulesByDependenciesProvider + * @dataProvider CheckMissingDependenciesAreCorrectlyOrderedInTheExceptionProvider */ - public function testOrderModulesByDependencies_CheckMissingDependenciesAreCorrectlyOrderedInTheException(int $mode) + public function testOrderModulesByDependencies_CheckMissingDependenciesAreCorrectlyOrderedInTheException(string $sMessage, bool $bIsLegacy) { $aModules=[ "id1/123" => [ @@ -39,92 +48,40 @@ class ModuleDiscoveryTest extends ItopDataTestCase 'label' => 'label2', ], ]; - $iLoopCount=0; - try{ - if ($mode===2){ - iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null, $iLoopCount); - } else { - if ($mode===1){ - ModuleDiscovery::UseNewUiFeedback(true); - } - ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } - } catch(\MissingDependencyException $e){ - if ($mode!==0) { - $sExpectedMessage = <<assertEquals($sExpectedMessage, $e->getMessage()); - if ($mode===2) { - $this->assertEquals(1, $iLoopCount); - } + $this->expectException(MissingDependencyException::class); + $this->expectExceptionMessage($sMessage); + + if ($bIsLegacy){ + ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + } else { + iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); } } - /** - * @dataProvider OrderModulesByDependenciesProvider - */ - public function testOrderModulesByDependencies_ValidateExceptionWithSomeDependenciesResolved(int $mode) + public static function ValidateExceptionWithSomeDependenciesResolvedProvider() { - $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', - ], - ]; - $iLoopCount=0; - try{ - if ($mode===2){ - iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null, $iLoopCount); - } else { - if ($mode===1){ - ModuleDiscovery::UseNewUiFeedback(true); - } - ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } - } catch(\MissingDependencyException $e){ - if ($mode!==0) { - $sExpectedMessage = <<assertEquals($sExpectedMessage, $e->getMessage()); - if ($mode===2) { - $this->assertEquals(2, $iLoopCount); - } - } + + return [ + 'legacy' => [ 'message' => $sLegacyExpectedMessage, 'is_legacy' => true ], + 'new computation' => [ 'message' => $sExpectedMessage, 'is_legacy' => false ], + ]; } /** - * @dataProvider OrderModulesByDependenciesProvider + * @dataProvider ValidateExceptionWithSomeDependenciesResolvedProvider */ - public function testOrderModulesByDependencies_KeepGoingEvenWithFailure_WithSomeDependenciesResolved(int $mode) + public function testOrderModulesByDependencies_ValidateExceptionWithSomeDependenciesResolved(string $sMessage, bool $bIsLegacy) { $aModules=[ "id1/123" => [ @@ -140,29 +97,72 @@ MSG; 'label' => 'label3', ], ]; - $iLoopCount=0; - if ($mode===2){ - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, false, null, $iLoopCount); + + $this->expectException(MissingDependencyException::class); + $this->expectExceptionMessage($sMessage); + + if ($bIsLegacy){ + ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); } else { - if ($mode===1){ - ModuleDiscovery::UseNewUiFeedback(true); - } - $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, false, null); + iTopCoreModuleDependencySort::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 = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, false, null); + $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, false, null); $aExpected = [ 'id2/456', ]; - $this->assertEquals($aExpected, array_keys($aResult)); - if ($mode===2) { - $this->assertEquals(2, $iLoopCount); - } + + $this->assertEquals($aExpected, array_keys($aLegacyResult)); + $this->assertEquals( $aLegacyResult, $aResult); + } + + public static function UnResolveWithCircularDependencyProvider() + { + $sExpectedMessage = << [ 'message' => $sLegacyExpectedMessage, 'is_legacy' => true ], + 'new computation' => [ 'message' => $sExpectedMessage, 'is_legacy' => false ], + ]; } /** - * @dataProvider OrderModulesByDependenciesProvider + * @dataProvider UnResolveWithCircularDependencyProvider */ - public function testOrderModulesByDependencies_UnResolveWithCircularDependency(int $mode) + public function testOrderModulesByDependencies_UnResolveWithCircularDependency(string $sMessage, bool $bIsLegacy) { $aModules=[ "id1/1" => [ @@ -182,46 +182,18 @@ MSG; 'label' => 'label4', ], ]; - $iLoopCount=0; - try{ - if ($mode===2){ - iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null, $iLoopCount); - } else { - if ($mode===1){ - ModuleDiscovery::UseNewUiFeedback(true); - } - ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } - } catch(\MissingDependencyException $e){ - if ($mode!==0) { - $sExpectedMessage = <<assertEquals($sExpectedMessage, $e->getMessage()); - if ($mode===2) { - $this->assertEquals(1, $iLoopCount); - } + $this->expectException(MissingDependencyException::class); + $this->expectExceptionMessage($sMessage); + + if ($bIsLegacy){ + ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + } else { + iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); } } - /** - * @dataProvider OrderModulesByDependenciesProvider - */ - public function testOrderModulesByDependencies_ResolveOk(int $mode) + public function testOrderModulesByDependencies_ResolveOk() { $aModules=[ "id0/1" => [ @@ -245,15 +217,6 @@ MSG; 'label' => 'label4', ], ]; - $iLoopCount=0; - if ($mode===2){ - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null, $iLoopCount); - } else { - if ($mode===1){ - ModuleDiscovery::UseNewUiFeedback(true); - } - $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } $aExpected = [ "id4/4", @@ -262,16 +225,15 @@ MSG; "id1/1", "id0/1", ]; - $this->assertEquals($aExpected, array_keys($aResult)); - if ($mode===2) { - $this->assertEquals(1, $iLoopCount); - } + + $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); + $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + + $this->assertEquals($aExpected, array_keys($aLegacyResult)); + $this->assertEquals( $aLegacyResult, $aResult); } - /** - * @dataProvider OrderModulesByDependenciesProvider - */ - public function testOrderModulesByDependencies_ResolveNoDependendenciesOrderByAlphabeticalOrder(int $mode) + public function testOrderModulesByDependencies_ResolveNoDependendenciesOrderByAlphabeticalOrder() { $aModules=[ "id2/2" => [ @@ -295,15 +257,6 @@ MSG; 'label' => 'label0', ], ]; - $iLoopCount=0; - if ($mode===2){ - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null, $iLoopCount); - } else { - if ($mode===1){ - ModuleDiscovery::UseNewUiFeedback(true); - } - $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); - } $aExpected = [ "id0/1", @@ -312,16 +265,15 @@ MSG; "id3/3", "id4/4", ]; - $this->assertEquals($aExpected, array_keys($aResult)); - if ($mode===2) { - $this->assertEquals(1, $iLoopCount); - } + + $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); + $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + + $this->assertEquals($aExpected, array_keys($aLegacyResult)); + $this->assertEquals( $aLegacyResult, $aResult); } - /** - * @dataProvider OrderModulesByDependenciesProvider - */ - public function testOrderModulesByDependencies_ResolveOk_ModulesToLoadProvided(int $mode) + public function testOrderModulesByDependencies_ResolveOk_ModulesToLoadProvided() { $aModules=[ "id1/1" => [ @@ -343,44 +295,29 @@ MSG; ]; foreach(["id3", "id3-itil"] as $sLastModuleNameToLoad) { - $iLoopCount = 0; - if ($mode===2){ - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad], $iLoopCount); - } else { - if ($mode===1){ - ModuleDiscovery::UseNewUiFeedback(true); - } - $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad]); - } - $aExpected = [ "$sLastModuleNameToLoad/3", "id2/2", "id1/1", ]; - $this->assertEquals($aExpected, array_keys($aResult)); - if ($mode===2) { - $this->assertEquals(1, $iLoopCount); - } + + $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad]); + $aLegacyResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, ['id1', 'id2', $sLastModuleNameToLoad]); + + $this->assertEquals($aExpected, array_keys($aLegacyResult)); + $this->assertEquals( $aLegacyResult, $aResult); } } - /*public function testOrderModulesByDependencies_RealExample(){ + public function testOrderModulesByDependenciesNewComputation_RealExample(){ $aModules = json_decode(file_get_contents(__DIR__ . '/ressources/module_deps.json'), true); - $aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, null); + + $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null); + $aLegacyResult = 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)); - }*/ - - public function testOrderModulesByDependenciesNewwComputation_RealExample(){ - $aModules = json_decode(file_get_contents(__DIR__ . '/ressources/module_deps.json'), true); - $iLoopCount=0; - $aResult = iTopCoreModuleDependencySort::OrderModulesByDependencies($aModules, true, null, $iLoopCount); - - $aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids.json'), true); - $this->assertEquals($aExpected, array_keys($aResult)); - $this->assertEquals(1, $iLoopCount); + $this->assertEquals( $aLegacyResult, $aResult); + $this->assertEquals($aExpected, array_keys($aLegacyResult)); } public function testSortModulesByCountOfDepencenciesDescending_NoDependencies(){ @@ -458,7 +395,7 @@ MSG; iTopCoreModuleDependencySort::GetInstance()->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules); - $aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids.json'), true); + $aExpected = json_decode(file_get_contents(__DIR__ . '/ressources/expected_ordered_module_ids2.json'), true); $this->assertEquals( $aExpected, array_keys($aUnresolvedDependencyModules)); diff --git a/tests/php-unit-tests/unitary-tests/setup/ressources/expected_ordered_module_ids.json b/tests/php-unit-tests/unitary-tests/setup/ressources/expected_ordered_module_ids.json index d52ca0903..2766580aa 100644 --- a/tests/php-unit-tests/unitary-tests/setup/ressources/expected_ordered_module_ids.json +++ b/tests/php-unit-tests/unitary-tests/setup/ressources/expected_ordered_module_ids.json @@ -1,49 +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" -] \ No newline at end of file +["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"] \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/ressources/expected_ordered_module_ids2.json b/tests/php-unit-tests/unitary-tests/setup/ressources/expected_ordered_module_ids2.json new file mode 100644 index 000000000..a8dd80bbc --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/ressources/expected_ordered_module_ids2.json @@ -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"] \ No newline at end of file