diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 3622c41930..1aafd90d08 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -2856,20 +2856,8 @@ class Config } } } - if (isset($aModuleInfo['installer'])) - { - $sModuleInstallerClass = $aModuleInfo['installer']; - if (!class_exists($sModuleInstallerClass)) - { - throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']); - } - if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) - { - throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']); - } - $aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig'); - call_user_func_array($aCallSpec, array($this)); - } + + ModuleDiscoveryService::GetInstance()->CallInstallerBeforeWritingConfigMethod($this, $aModuleInfo); } } $this->SetAddOns($aAddOns); diff --git a/setup/modulediscovery/ModuleDiscoveryService.php b/setup/modulediscovery/ModuleDiscoveryService.php index 1e8263de84..9f37adca4f 100644 --- a/setup/modulediscovery/ModuleDiscoveryService.php +++ b/setup/modulediscovery/ModuleDiscoveryService.php @@ -60,6 +60,8 @@ class ModuleDiscoveryService { { throw new ModuleDiscoveryServiceException("Eval of $sModuleFilePath did not return the expected information..."); } + + $this->AddModuleFilePath($aModuleInfo); } catch(ModuleDiscoveryServiceException $e) { @@ -79,6 +81,20 @@ class ModuleDiscoveryService { return $aModuleInfo; } + /** + * N°4789 - Parse datamodel module.xxx.php files instead of interpreting them + * additional path added to handle ModuleInstallerAPI declaration during setup only + * @param array &$aModuleInfo + * + * @return void + */ + private function AddModuleFilePath(array &$aModuleInfo) + { + if (count($aModuleInfo)==3) { + $aModuleInfo[2]['module_file_path'] = $aModuleInfo[0]; + } + } + /** * @param string $sPhpContent * @@ -112,6 +128,7 @@ class ModuleDiscoveryService { if ($oNode instanceof \PhpParser\Node\Stmt\Expression) { $aModuleConfig = $this->ParseCallToAddModuleAndReturnModuleConfiguration($sModuleFilePath, $oNode); if (! is_null($aModuleConfig)){ + $this->AddModuleFilePath($aModuleConfig); return $aModuleConfig; } } @@ -119,6 +136,7 @@ class ModuleDiscoveryService { if ($oNode instanceof PhpParser\Node\Stmt\If_) { $aModuleConfig = $this->BrowseIfStructure($sModuleFilePath, $oNode); if (! is_null($aModuleConfig)){ + $this->AddModuleFilePath($aModuleConfig); return $aModuleConfig; } } @@ -442,6 +460,37 @@ PHP; return (bool) $method->invokeArgs(null, $aArgs); } + + /** + * + * @param \Config $oConfig + * @param array $aModuleInfo + * + * @return void + * @throws \ModuleDiscoveryServiceException + */ + public function CallInstallerBeforeWritingConfigMethod(Config $oConfig, array $aModuleInfo) + { + if (isset($aModuleInfo['installer'])) + { + $sModuleInstallerClass = $aModuleInfo['installer']; + if (!class_exists($sModuleInstallerClass)) { + $sModuleFilePath = $aModuleInfo['module_file_path']; + $this->ReadModuleFileConfigurationLegacy($sModuleFilePath); + } + + if (!class_exists($sModuleInstallerClass)) + { + throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']); + } + if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) + { + throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']); + } + $aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig'); + call_user_func_array($aCallSpec, array($oConfig)); + } + } } class ModuleDiscoveryServiceException extends Exception diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleDiscoveryServiceTest.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleDiscoveryServiceTest.php index 4044e12206..e68b3cbf86 100644 --- a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleDiscoveryServiceTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleDiscoveryServiceTest.php @@ -108,19 +108,19 @@ class ModuleDiscoveryServiceTest extends ItopDataTestCase return [ "simple call to SetupInfo::ModuleIsSelected SELECTED" => [ "expr" => $sSimpleCallToModuleIsSelected, - "expected" => true + "expected" => true, ], "simple call to SetupInfo::ModuleIsSelected NOT SELECTED" => [ "expr" => $sSimpleCallToModuleIsSelected2, - "expected" => false + "expected" => false, ], "call to SetupInfo::ModuleIsSelected + OR => SELECTED" => [ "expr" => $sCallToModuleIsSelectedCombinedWithAndOperator, - "expected" => true + "expected" => true, ], "simple call to SetupInfo::ModuleIsSelected + OR => NOT SELECTED" => [ "expr" => $sCallToModuleIsSelectedCombinedWithAndOperator2, - "expected" => false + "expected" => false, ], ]; } @@ -152,12 +152,11 @@ PHP; $this->sTempModuleFilePath = tempnam(__DIR__, "test"); file_put_contents($this->sTempModuleFilePath, $sPHpCode); try { - return $this->InvokeNonPublicMethod(ModuleDiscoveryService::class, "ReadModuleFileConfiguration", ModuleDiscoveryService::GetInstance(), [$this->sTempModuleFilePath]); + return ModuleDiscoveryService::GetInstance()->ReadModuleFileConfiguration($this->sTempModuleFilePath); } finally { @unlink($this->sTempModuleFilePath); } - } public function testReadModuleFileConfigurationCheckBasicStatementWithoutIf() @@ -169,7 +168,7 @@ SetupWebPage::AddModule("a", "noif", ["c" => "d"]); \$b=2; PHP; $val = $this->CallReadModuleFileConfiguration($sPHP); - $this->assertEquals([$this->sTempModuleFilePath, "noif", ["c" => "d"]], $val); + $this->assertEquals([$this->sTempModuleFilePath, "noif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); } public function testReadModuleFileConfigurationCheckBasicStatement_IfConditionVerified() @@ -190,7 +189,7 @@ SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); \$b=2; PHP; $val = $this->CallReadModuleFileConfiguration($sPHP); - $this->assertEquals([$this->sTempModuleFilePath, "if", ["c" => "d"]], $val); + $this->assertEquals([$this->sTempModuleFilePath, "if", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); } public function testReadModuleFileConfigurationCheckBasicStatement_IfNoConditionVerifiedAndNoElse() @@ -209,7 +208,7 @@ SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); \$b=2; PHP; $val = $this->CallReadModuleFileConfiguration($sPHP); - $this->assertEquals([$this->sTempModuleFilePath, "outsideif", ["c" => "d"]], $val); + $this->assertEquals([$this->sTempModuleFilePath, "outsideif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); } public function testReadModuleFileConfigurationCheckBasicStatement_ElseApplied() @@ -230,7 +229,7 @@ SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); \$b=2; PHP; $val = $this->CallReadModuleFileConfiguration($sPHP); - $this->assertEquals([$this->sTempModuleFilePath, "else", ["c" => "d"]], $val); + $this->assertEquals([$this->sTempModuleFilePath, "else", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); } public function testReadModuleFileConfigurationCheckBasicStatement_FirstElseIfApplied() @@ -251,7 +250,7 @@ SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); \$b=2; PHP; $val = $this->CallReadModuleFileConfiguration($sPHP); - $this->assertEquals([$this->sTempModuleFilePath, "elseif1", ["c" => "d"]], $val); + $this->assertEquals([$this->sTempModuleFilePath, "elseif1", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); } public function testReadModuleFileConfigurationCheckBasicStatement_LastElseIfApplied() @@ -272,7 +271,7 @@ SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); \$b=2; PHP; $val = $this->CallReadModuleFileConfiguration($sPHP); - $this->assertEquals([$this->sTempModuleFilePath, "elseif2", ["c" => "d"]], $val); + $this->assertEquals([$this->sTempModuleFilePath, "elseif2", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); } public static function EvaluateExpressionBooleanProvider() { @@ -286,85 +285,85 @@ PHP; return [ "true" => [ "code" => str_replace("COND", "true", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "false" => [ "code" => str_replace("COND", "false", $sTruePHP), - "bool_expected" => false + "bool_expected" => false, ], "not ok" => [ "code" => str_replace("COND", "! false", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "not ko" => [ "code" => str_replace("COND", "! (true)", $sTruePHP), - "bool_expected" => false + "bool_expected" => false, ], "AND ko" => [ "code" => str_replace("COND", "true && false", $sTruePHP), - "bool_expected" => false + "bool_expected" => false, ], "AND ok1" => [ "code" => str_replace("COND", "true && true", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "AND ko2" => [ "code" => str_replace("COND", "true && true && false", $sTruePHP), - "bool_expected" => false + "bool_expected" => false, ], "OR ko" => [ "code" => str_replace("COND", "false || false", $sTruePHP), - "bool_expected" => false + "bool_expected" => false, ], "OR ok" => [ "code" => str_replace("COND", "false ||true", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "OR ok2" => [ "code" => str_replace("COND", "false ||false||true", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "function_exists('ldap_connect')" => [ "code" => str_replace("COND", "function_exists('ldap_connect')", $sTruePHP), - "bool_expected" => function_exists('ldap_connect') + "bool_expected" => function_exists('ldap_connect'), ], "function_exists('gabuzomeushouldnotexist')" => [ "code" => str_replace("COND", "function_exists('gabuzomeushouldnotexist')", $sTruePHP), - "bool_expected" => function_exists('gabuzomeushouldnotexist') + "bool_expected" => function_exists('gabuzomeushouldnotexist'), ], "1 > 2" => [ "code" => str_replace("COND", "1 > 2", $sTruePHP), - "bool_expected" => false + "bool_expected" => false, ], "1 == 1" => [ "code" => str_replace("COND", "1 == 1", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "1 < 2" => [ "code" => str_replace("COND", "1 < 2", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "PHP_VERSION_ID == PHP_VERSION_ID" => [ "code" => str_replace("COND", "PHP_VERSION_ID == PHP_VERSION_ID", $sTruePHP), - "bool_expected" => true + "bool_expected" => true, ], "PHP_VERSION_ID != PHP_VERSION_ID" => [ "code" => str_replace("COND", "PHP_VERSION_ID != PHP_VERSION_ID", $sTruePHP), - "bool_expected" => false + "bool_expected" => false, ], ]; } @@ -380,4 +379,27 @@ PHP; $val = $this->InvokeNonPublicMethod(ModuleDiscoveryService::class, "EvaluateBooleanExpression", ModuleDiscoveryService::GetInstance(), [$oExpr->cond]); $this->assertEquals($bExpected, $val); } + + public function testCallDeclaredInstaller() + { + $sModuleInstallerClass = "TicketsInstaller" . uniqid(); + $sPHpCode = file_get_contents(__DIR__.'/resources/module.itop-tickets.php'); + $sPHpCode = str_replace("TicketsInstaller", $sModuleInstallerClass, $sPHpCode); + $this->sTempModuleFilePath = tempnam(__DIR__, "test"); + file_put_contents($this->sTempModuleFilePath, $sPHpCode); + var_dump($sPHpCode); + + try { + $this->assertFalse(class_exists($sModuleInstallerClass)); + $aModuleInfo = ModuleDiscoveryService::GetInstance()->ReadModuleFileConfiguration($this->sTempModuleFilePath); + $this->assertFalse(class_exists($sModuleInstallerClass)); + + ModuleDiscoveryService::GetInstance()->CallInstallerBeforeWritingConfigMethod(\MetaModel::GetConfig(), $aModuleInfo[2]); + } + finally { + @unlink($this->sTempModuleFilePath); + } + + $this->assertTrue(class_exists($sModuleInstallerClass)); + } } \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-tickets.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-tickets.php new file mode 100644 index 0000000000..f46fc1f231 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-tickets.php @@ -0,0 +1,107 @@ + 'Tickets Management', + 'category' => 'business', + + // Setup + // + 'dependencies' => array( + 'itop-structure/2.7.1', + ), + 'mandatory' => false, + 'visible' => true, + 'installer' => 'TicketsInstaller', + + // Components + // + 'datamodel' => array( + 'main.itop-tickets.php', + ), + 'data.struct' => array( + // 'data.struct.ta-actions.xml', + ), + 'data.sample' => array( + ), + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => array( + ), + ) +); + +// Module installation handler +// +class TicketsInstaller extends ModuleInstallerAPI +{ + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + // Delete all Triggers corresponding to a no more valid class + CMDBObject::SetTrackInfo('Uninstallation'); + $oSearch = new DBObjectSearch('TriggerOnObject'); + $oSet = new DBObjectSet($oSearch); + while($oTrigger = $oSet->Fetch()) + { + try + { + if (!MetaModel::IsValidClass($oTrigger->Get('target_class'))) + { + $oTrigger->DBDelete(); + } + } + catch(Exception $e) + { + utils::EnrichRaisedException($oTrigger, $e); + } + } + // It's not very clear if it make sense to test a particular version, + // as the loading mechanism checks object existence using reconc_keys + // and do not recreate them, nor update existing. + // Without test, new entries added to the data files, would be automatically loaded + if (($sPreviousVersion === '') || + (version_compare($sPreviousVersion, $sCurrentVersion, '<') + && version_compare($sPreviousVersion, '3.0.0', '<'))) { + $oDataLoader = new XMLDataLoader(); + + CMDBObject::SetTrackInfo("Initialization TicketsInstaller"); + $oMyChange = CMDBObject::GetCurrentChange(); + + $sLang = null; + // - Try to get app. language from configuration fil (app. upgrade) + $sConfigFileName = APPCONF.'production/'.ITOP_CONFIG_FILE; + if (file_exists($sConfigFileName)) { + $oFileConfig = new Config($sConfigFileName); + if (is_object($oFileConfig)) { + $sLang = str_replace(' ', '_', strtolower($oFileConfig->GetDefaultLanguage())); + } + } + + // - I still no language, get the default one + if (null === $sLang) { + $sLang = str_replace(' ', '_', strtolower($oConfiguration->GetDefaultLanguage())); + } + + $sFileName = dirname(__FILE__)."/data/{$sLang}.data.itop-tickets.xml"; + SetupLog::Info("Searching file: $sFileName"); + if (!file_exists($sFileName)) { + $sFileName = dirname(__FILE__)."/data/en_us.data.itop-tickets.xml"; + } + SetupLog::Info("Loading file: $sFileName"); + $oDataLoader->StartSession($oMyChange); + $oDataLoader->LoadFile($sFileName, false, true); + $oDataLoader->EndSession(); + } + } +}