N°4789 - Parse datamodel module.xxx.php files instead of interpreting them (#746)

* N°4789 - Parse datamodel module.xxx.php files instead of interpreting them - refactoring all in a dedicated service first

* N°4789 - fix broken setup + tests

* N°4789 - replace legacy eval by module file parsing

* N°4789 - handle constants and if conditional structures

* N°4789 - compute boolean expressions

* N°4789 - make autoselect and dependencies work as well

* cleanup

* N°4789 - fix BeforeWritingConfig calls during setup

* N°4789 - refactor and split in ModuleDiscoveryEvaluationService + handle ModuleInstallerAPI methods calls during setup

* N°4789 - PR review changes with Romain

* PR review + code cleanup + added usecases and test cover

* temp evaluation work

* replace eval by iTop custom evaluation classes

* move PhpParser/Evaluation classes in a specific namespave + composer dumpautoload

* fix broken setup

* fix broken setup

* complete Evaluators list + autoload

* cleanup useless testing resources

* cleanup + replace last eval call in VariableEvaluator

* fix few Evaluators code

* enhance nikic evaluators + test with/without nikic lib

* Evaluator fixes/enhancements + tests

* bump to nikic fork temporarly

* bump nikic-parser fork + use only nikic fork  evaluation + cleanup itop redondant evaluators

* review with Romain: use distinct whitelists in setup time/runtime + move ModuleFileParser internal logic into ModuleFileReader

* PhpExpressionEvaluator used via constructor and not as a service

* dumpautoload again after rebase
This commit is contained in:
odain-cbd
2025-09-09 17:54:18 +02:00
committed by GitHub
parent 2ee68ff819
commit 15103dc49f
51 changed files with 3199 additions and 1334 deletions

View File

@@ -0,0 +1,220 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ModuleFileReader;
class ModuleFileReaderTest extends ItopDataTestCase
{
private string $sTempModuleFilePath;
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('setup/modulediscovery/ModuleFileReader.php');
}
public function testReadModuleFileInformationUnsafe()
{
$sModuleFilePath = __DIR__.'/resources/module.itop-full-itil.php';
$aRes = ModuleFileReader::GetInstance()->ReadModuleFileInformationUnsafe($sModuleFilePath);
$this->assertCount(3, $aRes);
$this->assertEquals($sModuleFilePath, $aRes[0]);
$this->assertEquals('itop-full-itil/3.3.0', $aRes[1]);
$this->assertIsArray($aRes[2]);
$this->assertArrayHasKey('label', $aRes[2]);
$this->assertEquals('Bridge - Request management ITIL + Incident management ITIL', $aRes[2]['label'] ?? null);
}
public static function ReadModuleFileConfigurationFileNameProvider()
{
$aUsecases=[];
foreach (glob(__DIR__.'/resources/*.php') as $sModuleFilePath){
if (false !== strpos($sModuleFilePath, "module.__MODULE__.php")){
continue;
}
$aUsecases[basename($sModuleFilePath)]=[$sModuleFilePath];
}
return $aUsecases;
}
/**
* @dataProvider ReadModuleFileConfigurationFileNameProvider
*/
public function testReadModuleFileConfigurationVsLegacyMethod(string $sModuleFilePath)
{
$_SERVER=[
'SERVER_NAME' => 'titi'
];
$aRes = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
$aExpected = ModuleFileReader::GetInstance()->ReadModuleFileInformationUnsafe($sModuleFilePath);
$this->assertEquals($aExpected, $aRes);
}
public function testReadModuleFileConfigurationParsingIssue()
{
$sModuleFilePath = __DIR__.'/resources/module.__MODULE__.php';
$this->expectException(\ModuleFileReaderException::class);
$this->expectExceptionMessage("Syntax error, unexpected T_CONSTANT_ENCAPSED_STRING, expecting ',' or ']' or ')' on line 31");
ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
}
/**
* local tool function
*/
private function CallReadModuleFileConfiguration($sPHpCode)
{
$this->sTempModuleFilePath = tempnam(__DIR__, "test");
file_put_contents($this->sTempModuleFilePath, $sPHpCode);
try {
return ModuleFileReader::GetInstance()->ReadModuleFileInformation($this->sTempModuleFilePath);
}
finally {
@unlink($this->sTempModuleFilePath);
}
}
public function testReadModuleFileConfigurationCheckBasicStatementWithoutIf()
{
$sPHP = <<<PHP
<?php
\$a=1;
SetupWebPage::AddModule("a", "noif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "noif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_IfConditionVerified()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (true){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "if", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_IfNoConditionVerifiedAndNoElse()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "outsideif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_ElseApplied()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "else", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_FirstElseIfApplied()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "elseif1", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_LastElseIfApplied()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "elseif2", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testGetAndCheckModuleInstallerClass()
{
$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 = ModuleFileReader::GetInstance()->ReadModuleFileInformation($this->sTempModuleFilePath);
$this->assertFalse(class_exists($sModuleInstallerClass));
$this->assertEquals($sModuleInstallerClass, ModuleFileReader::GetInstance()->GetAndCheckModuleInstallerClass($aModuleInfo[2]));
}
finally {
@unlink($this->sTempModuleFilePath);
}
$this->assertTrue(class_exists($sModuleInstallerClass));
}
}