diff --git a/core/config.class.inc.php b/core/config.class.inc.php index fae11cc153..fb7f1dc330 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -20,6 +20,16 @@ * */ +use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator; +use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException; +use PhpParser\Comment; +use PhpParser\Error; +use PhpParser\Node\Arg; +use PhpParser\Node\Expr\Array_; +use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\Variable; +use PhpParser\ParserFactory; + define('ITOP_APPLICATION', 'iTop'); define('ITOP_APPLICATION_SHORT', 'iTop'); @@ -2124,6 +2134,29 @@ class Config eval('?'.'>'.trim($sConfigCode)); $sNoise = trim(ob_get_contents()); ob_end_clean(); + + try { + $oParser = (new ParserFactory())->createForNewestSupportedVersion(); + foreach ($oParser->parse($sConfigCode) as $oNode) { + if ($oNode instanceof \PhpParser\Node\Stmt\Expression) { + /** @var \PhpParser\Node\Stmt\Expression $oNode */ + $oExpr = $oNode->expr; + if ($oExpr instanceof Assign) { + /** @var Assign $oExpr */ + $oVar = $oExpr->var; + if ($oVar instanceof Variable && $oVar->name === "MyModuleSettings") { + if ($oExpr->expr instanceof Array_) { + $oPhpExpressionEvaluator = new PhpExpressionEvaluator(); + $aArrayWithComments = $oPhpExpressionEvaluator->GetArrayWithComments($oExpr->expr); + $MyModuleSettings = array_replace_recursive($aArrayWithComments, $MyModuleSettings); + } + } + } + } + } + } catch (Error $e) { + var_dump($e); + } } catch (Error $e) { // PHP 7 throw new ConfigException( @@ -2714,6 +2747,13 @@ class Config foreach ($this->m_aModuleSettings as $sModule => $aProperties) { fwrite($hFile, "\t'$sModule' => array (\n"); foreach ($aProperties as $sProperty => $value) { + if (is_string($value) && false !== strpos($value, 'PhpParserComment')) { + $value = preg_replace(["/.*StartPhpParserComment/", "/EndPhpParserComment/"], + ['', ''], + $value); + fwrite($hFile, "\t\t$value\n"); + continue; + } $sNiceExport = self::PrettyVarExport($this->oItopConfigParser->GetVarValue('MyModuleSettings', $sProperty), $value, "\t\t"); fwrite($hFile, "\t\t'$sProperty' => $sNiceExport,\n"); } @@ -2883,7 +2923,7 @@ class Config } /** - * Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line + * Pretty format a var_export'ed value so that (if possible) the indentation is preserved on every line * * @param array $aParserValue * @param mixed $value The value to export @@ -2900,12 +2940,17 @@ class Config } $sExport = var_export($value, true); + if (strpos($sExport, 'PhpParserComment')) { + $sExport = preg_replace(["/.*StartPhpParserComment/", "/EndPhpParserComment',/"], + ['', ''], + $sExport); + } $sNiceExport = str_replace(["\r\n", "\n", "\r"], "\n".$sIndentation, trim($sExport)); if (!$bForceIndentation) { /** @var array $aImported */ $aImported = null; eval('$aImported='.$sNiceExport.';'); - // Check if adding the identations at the beginning of each line + // Check if adding the indentations at the beginning of each line // did not modify the values (in case of a string containing a line break) if ($aImported != $value) { $sNiceExport = $sExport; @@ -2914,7 +2959,6 @@ class Config return $sNiceExport; } - } class ConfigPlaceholdersResolver diff --git a/setup/modulediscovery/ModuleFileReader.php b/setup/modulediscovery/ModuleFileReader.php index b87df88954..5216d6899b 100644 --- a/setup/modulediscovery/ModuleFileReader.php +++ b/setup/modulediscovery/ModuleFileReader.php @@ -260,6 +260,11 @@ class ModuleFileReader } $aModuleConfig = $this->oPhpExpressionEvaluator->EvaluateExpression($oModuleConfigInfo->value); + if (isset($aModuleConfig['settings'])) { + $oPhpExpressionEvaluator = new PhpExpressionEvaluator(); + $aArrayWithComments = $oPhpExpressionEvaluator->GetArrayWithComments($oModuleConfigInfo->value); + $aModuleConfig['settings'] = array_replace_recursive($aArrayWithComments['settings'], $aModuleConfig['settings']); + } if (! is_array($aModuleConfig)) { throw new ModuleFileReaderException("3rd parameter to SetupWebPage::AddModule not an array: ".get_class($oModuleConfigInfo->value), 0, null, $sModuleFilePath); diff --git a/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php b/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php index f87284272c..33292baaa7 100644 --- a/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php +++ b/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php @@ -4,9 +4,11 @@ namespace Combodo\iTop\PhpParser\Evaluation; use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileParser; use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException; +use PhpParser\Comment; use PhpParser\ConstExprEvaluator; use PhpParser\ExprEvaluator; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Array_; use PhpParser\ParserFactory; /** @@ -55,4 +57,31 @@ PHP; throw new ModuleFileReaderException("Eval of '$sExpr' caused an error:".$t->getMessage()); } } + + public function GetArrayWithComments(Array_ $oArray): array + { + $aRes = []; + $i=0; + foreach ($oArray->items as $oItem) { + /** @var \PhpParser\Node\ArrayItem $oItem **/ + if(is_null($oItem->key)){ + $sKey = $i; + $i++; + } else { + $sKey = $this->EvaluateExpression($oItem->key); + } + foreach ($oItem->getComments() as $oComment) { + /** @var \PhpParser\Comment $oComment */ + $aRes[] = 'StartPhpParserComment'.$oComment->getText().'EndPhpParserComment'; + } + if ($oItem->value instanceof Array_) { + $aRes[$sKey] = $this->GetArrayWithComments($oItem->value); + } elseif ($oItem->value instanceof Comment) { + $aRes[$sKey] = $oItem->value; + } else { + $aRes[$sKey] = $this->EvaluateExpression($oItem->value); + } + } + return $aRes; + } } diff --git a/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-config/ConfigTest.php b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-config/ConfigTest.php index 2252095539..c1c8b16fac 100644 --- a/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-config/ConfigTest.php +++ b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-config/ConfigTest.php @@ -72,7 +72,6 @@ class ConfigTest extends ItopTestCase 'sExpectedContains' => "'app_root_url' => 'http://%server(SERVER_NAME)?:localhost%/itop/iTop/'", 'aChanges' => [], ], - 'preserve set same value' => [ 'sConfigFile' => __DIR__.'/ConfigTest/config-itop-var.php', 'sExpectedContains' => "'app_root_url' => 'http://' . (isset(\$_SERVER['SERVER_NAME']) ? \$_SERVER['SERVER_NAME'] : 'localhost') . '/itop/iTop/'", @@ -91,4 +90,21 @@ class ConfigTest extends ItopTestCase ], ]; } + + public function testPreserveModuleSettingsOnWriteToFile() + { + $sTmpFile = tempnam(sys_get_temp_dir(), "target"); + + $sConfigFile = __DIR__.'/ConfigTest/config-itop-modulesetting.php'; + $oConfig = new Config($sConfigFile); + $oConfig->WriteToFile($sTmpFile); + + $this->assertFileExists($sTmpFile); + $this->assertEquals($this->GetModuleSettingSection($sConfigFile), $this->GetModuleSettingSection($sTmpFile)); + } + + private function GetModuleSettingSection(string $sFilePath) : string { + preg_match('/\$MyModuleSettings[\w\W]*\/\*\*/m', file_get_contents($sFilePath), $aMatches); + return preg_replace(['/[ ]+/', '/[ ]+/'],[' ', ' '], $aMatches[0]); + } } diff --git a/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-config/ConfigTest/config-itop-modulesetting.php b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-config/ConfigTest/config-itop-modulesetting.php new file mode 100644 index 0000000000..2f98a5e61d --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/datamodels/2.x/itop-config/ConfigTest/config-itop-modulesetting.php @@ -0,0 +1,54 @@ + array ( + //debug to add traces... + 'debug' => false, + 'providers' => array ( + 'Keycloak' => + /* + ga + bu + zo meu + */ + array ( + 'keys' => + array ( + /** + * sha + * + * dok + */ + 'id' => 'my-clientid', + 'secret' => 'my-secret', + ), + 'enabled' => false, + ), + //url to access IdP + 'url' => 'keycloak_url', + ), + ), +); + +/** + * + * Data model modules to be loaded. Names are specified as relative paths + * + */ +$MyModules = [ +];