mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-20 00:58:48 +02:00
N°4789 - compute boolean expressions
This commit is contained in:
@@ -79,6 +79,17 @@ class ModuleDiscoveryService {
|
||||
return $aModuleInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPhpContent
|
||||
*
|
||||
* @return \PhpParser\Node\Stmt[]|null
|
||||
*/
|
||||
public function parsePhpCode(string $sPhpContent): ?array
|
||||
{
|
||||
$oParser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
return $oParser->parse($sPhpContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the information from a module file (module.xxx.php)
|
||||
* Closely inspired (almost copied/pasted !!) from ModuleDiscovery::ListModuleFiles
|
||||
@@ -90,8 +101,7 @@ class ModuleDiscoveryService {
|
||||
{
|
||||
try
|
||||
{
|
||||
$oParser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
$aNodes = $oParser->parse(file_get_contents($sModuleFilePath));
|
||||
$aNodes = $this->parsePhpCode(file_get_contents($sModuleFilePath));
|
||||
}
|
||||
catch (PhpParser\Error $e) {
|
||||
throw new \ModuleDiscoveryServiceException($e->getMessage(), 0, $e, $sModuleFilePath);
|
||||
@@ -124,24 +134,6 @@ class ModuleDiscoveryService {
|
||||
throw new ModuleDiscoveryServiceException("No proper call to SetupWebPage::AddModule found in module file", 0, null, $sModuleFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sBooleanExpr
|
||||
*
|
||||
* @return bool
|
||||
* @throws ModuleDiscoveryServiceException
|
||||
*/
|
||||
public function ComputeBooleanExpression(string $sBooleanExpr) : bool
|
||||
{
|
||||
$bResult = false;
|
||||
try{
|
||||
@eval('$bResult = '.$sBooleanExpr.';');
|
||||
} catch (Throwable $t) {
|
||||
throw new ModuleDiscoveryServiceException("Eval of '$sBooleanExpr' caused an error: ".$t->getMessage());
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
private function BrowseArrayStructure(PhpParser\Node\Expr\Array_ $oArray, array &$aModuleConfig) : void
|
||||
{
|
||||
$iIndex=0;
|
||||
@@ -242,7 +234,7 @@ class ModuleDiscoveryService {
|
||||
return [
|
||||
$sModuleFilePath,
|
||||
$sModuleId,
|
||||
$aModuleConfig
|
||||
$aModuleConfig,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -268,20 +260,27 @@ class ModuleDiscoveryService {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($oNode->elseifs as $oElseIfSubNode) {
|
||||
/** @var \PhpParser\Node\Stmt\ElseIf_ $oElseIfSubNode*/
|
||||
$bCondition = $this->EvaluateBooleanExpression($oElseIfSubNode->cond);
|
||||
if($bCondition){
|
||||
$aModuleConfig = $this->ParseStatementsAndReturnModuleConfiguration($sModuleFilePath, $oNode->stmts);
|
||||
if (!is_null($aModuleConfig)) {
|
||||
return $aModuleConfig;
|
||||
if (! is_null($oNode->elseifs)) {
|
||||
foreach ($oNode->elseifs as $oElseIfSubNode) {
|
||||
/** @var \PhpParser\Node\Stmt\ElseIf_ $oElseIfSubNode */
|
||||
$bCondition = $this->EvaluateBooleanExpression($oElseIfSubNode->cond);
|
||||
if ($bCondition) {
|
||||
$aModuleConfig = $this->ParseStatementsAndReturnModuleConfiguration($sModuleFilePath, $oElseIfSubNode->stmts);
|
||||
if (!is_null($aModuleConfig)) {
|
||||
return $aModuleConfig;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$aModuleConfig = $this->ParseStatementsAndReturnModuleConfiguration($sModuleFilePath, $oNode->else->stmts);
|
||||
return $aModuleConfig;
|
||||
if (! is_null($oNode->else)) {
|
||||
$aModuleConfig = $this->ParseStatementsAndReturnModuleConfiguration($sModuleFilePath, $oNode->else->stmts);
|
||||
|
||||
return $aModuleConfig;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -311,11 +310,96 @@ class ModuleDiscoveryService {
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
private function GetMixedValueForBooleanOperatorEvaluation(\PhpParser\Node\Expr $oExpr) : string
|
||||
{
|
||||
if ($oExpr instanceof \PhpParser\Node\Scalar\Int_ || $oExpr instanceof \PhpParser\Node\Scalar\Float_){
|
||||
return "" . $oExpr->value;
|
||||
}
|
||||
|
||||
return $this->EvaluateBooleanExpression($oExpr) ? "true" : "false";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sBooleanExpr
|
||||
*
|
||||
* @return bool
|
||||
* @throws ModuleDiscoveryServiceException
|
||||
*/
|
||||
public function ComputeBooleanExpression(string $sBooleanExpr) : bool
|
||||
{
|
||||
$bResult = false;
|
||||
try{
|
||||
@eval('$bResult = '.$sBooleanExpr.';');
|
||||
} catch (Throwable $t) {
|
||||
throw new ModuleDiscoveryServiceException("Eval of '$sBooleanExpr' caused an error: ".$t->getMessage());
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sBooleanExpr
|
||||
*
|
||||
* @return bool
|
||||
* @throws ModuleDiscoveryServiceException
|
||||
*/
|
||||
public function ComputeBooleanExpression3(string $sBooleanExpr) : bool
|
||||
{
|
||||
$sPhpContent = <<<PHP
|
||||
<?php
|
||||
$sBooleanExpr;
|
||||
PHP;
|
||||
$aNodes = ModuleDiscoveryService::GetInstance()->parsePhpCode($sPhpContent);
|
||||
$oExpr = $aNodes[0];
|
||||
return $this->EvaluateBooleanExpression($oExpr->expr);
|
||||
}
|
||||
|
||||
private function EvaluateBooleanExpression(\PhpParser\Node\Expr $oCondExpression) : bool
|
||||
{
|
||||
//var_dump($oCondExpression);
|
||||
|
||||
if ($oCondExpression instanceof \PhpParser\Node\Expr\BinaryOp){
|
||||
$sExpr = $this->GetMixedValueForBooleanOperatorEvaluation($oCondExpression->left)
|
||||
. " "
|
||||
. $oCondExpression->getOperatorSigil()
|
||||
. " "
|
||||
. $this->GetMixedValueForBooleanOperatorEvaluation($oCondExpression->right);
|
||||
return $this->ComputeBooleanExpression($sExpr);
|
||||
}
|
||||
|
||||
if ($oCondExpression instanceof \PhpParser\Node\Expr\BooleanNot){
|
||||
return ! $this->EvaluateBooleanExpression($oCondExpression->expr);
|
||||
}
|
||||
|
||||
if ($oCondExpression instanceof \PhpParser\Node\Expr\FuncCall){
|
||||
return $this->CallFunction($oCondExpression);
|
||||
}
|
||||
|
||||
if ($oCondExpression instanceof \PhpParser\Node\Expr\ConstFetch){
|
||||
return $this->EvaluateConstantExpression($oCondExpression);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function CallFunction(\PhpParser\Node\Expr\FuncCall $oFunct) : bool
|
||||
{
|
||||
$sFunction = $oFunct->name->name;
|
||||
$aWhiteList = ["function_exists"];
|
||||
if (! in_array($sFunction, $aWhiteList)){
|
||||
throw new ModuleDiscoveryServiceException("FuncCall $sFunction not supported");
|
||||
//return false;
|
||||
}
|
||||
|
||||
$aArgs=[];
|
||||
foreach ($oFunct->args as $arg){
|
||||
/** @var \PhpParser\Node\Arg $arg */
|
||||
$aArgs[]=$arg->value->value;
|
||||
}
|
||||
|
||||
$oReflectionFunction = new ReflectionFunction($sFunction);
|
||||
return (bool)$oReflectionFunction->invoke(...$aArgs);
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleDiscoveryServiceException extends Exception
|
||||
|
||||
@@ -4,9 +4,11 @@ namespace Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ModuleDiscoveryService;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
class ModuleDiscoveryServiceTest extends ItopDataTestCase
|
||||
{
|
||||
private string $sTempModuleFilePath;
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -26,6 +28,16 @@ class ModuleDiscoveryServiceTest extends ItopDataTestCase
|
||||
$this->assertEquals('Bridge - Request management ITIL + Incident management ITIL', $aRes[2]['label'] ?? null);
|
||||
}
|
||||
|
||||
/*public function testAllReadModuleFileConfiguration()
|
||||
{
|
||||
foreach (glob(__DIR__.'/resources/all/module.*.php') as $sModuleFilePath){
|
||||
$aRes = ModuleDiscoveryService::GetInstance()->ReadModuleFileConfiguration($sModuleFilePath);
|
||||
$aExpected = ModuleDiscoveryService::GetInstance()->ReadModuleFileConfigurationLegacy($sModuleFilePath);
|
||||
|
||||
$this->assertEquals($aExpected, $aRes);
|
||||
}
|
||||
}*/
|
||||
|
||||
public function testReadModuleFileConfiguration()
|
||||
{
|
||||
$sModuleFilePath = __DIR__.'/resources/module.itop-full-itil.php';
|
||||
@@ -79,4 +91,251 @@ class ModuleDiscoveryServiceTest extends ItopDataTestCase
|
||||
$this->expectExceptionMessage('Eval of \'(a || true)\' caused an error: Undefined constant "a"');
|
||||
$this->assertTrue(ModuleDiscoveryService::GetInstance()->ComputeBooleanExpression("(a || true)"));
|
||||
}
|
||||
|
||||
public function testEvaluateConstantExpression()
|
||||
{
|
||||
$sPHP = <<<PHP
|
||||
<?php
|
||||
APPROOT;
|
||||
PHP;
|
||||
$aNodes = ModuleDiscoveryService::GetInstance()->parsePhpCode($sPHP);
|
||||
/** @var \PhpParser\Node\Expr $oExpr */
|
||||
$oExpr = $aNodes[0];
|
||||
$val = $this->InvokeNonPublicMethod(ModuleDiscoveryService::class, "EvaluateConstantExpression", ModuleDiscoveryService::GetInstance(), [$oExpr->expr]);
|
||||
$this->assertEquals(APPROOT, $val);
|
||||
}
|
||||
|
||||
public function CallReadModuleFileConfiguration($sPHpCode)
|
||||
{
|
||||
$this->sTempModuleFilePath = tempnam(__DIR__, "test");
|
||||
file_put_contents($this->sTempModuleFilePath, $sPHpCode);
|
||||
try {
|
||||
return $this->InvokeNonPublicMethod(ModuleDiscoveryService::class, "ReadModuleFileConfiguration", ModuleDiscoveryService::GetInstance(), [$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"]], $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"]], $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"]], $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"]], $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"]], $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"]], $val);
|
||||
}
|
||||
|
||||
public static function EvaluateExpressionBooleanProvider() {
|
||||
$sTruePHP = <<<PHP
|
||||
<?php
|
||||
if (COND){
|
||||
echo "toto";
|
||||
}
|
||||
PHP;
|
||||
|
||||
return [
|
||||
"true" => [
|
||||
"code" => str_replace("COND", "true", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
|
||||
],
|
||||
"false" => [
|
||||
"code" => str_replace("COND", "false", $sTruePHP),
|
||||
"bool_expected" => false
|
||||
|
||||
],
|
||||
"not ok" => [
|
||||
"code" => str_replace("COND", "! false", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
|
||||
],
|
||||
"not ko" => [
|
||||
"code" => str_replace("COND", "! (true)", $sTruePHP),
|
||||
"bool_expected" => false
|
||||
|
||||
],
|
||||
"AND ko" => [
|
||||
"code" => str_replace("COND", "true && false", $sTruePHP),
|
||||
"bool_expected" => false
|
||||
|
||||
],
|
||||
"AND ok1" => [
|
||||
"code" => str_replace("COND", "true && true", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
|
||||
],
|
||||
"AND ko2" => [
|
||||
"code" => str_replace("COND", "true && true && false", $sTruePHP),
|
||||
"bool_expected" => false
|
||||
|
||||
],
|
||||
"OR ko" => [
|
||||
"code" => str_replace("COND", "false || false", $sTruePHP),
|
||||
"bool_expected" => false
|
||||
|
||||
],
|
||||
"OR ok" => [
|
||||
"code" => str_replace("COND", "false ||true", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
|
||||
],
|
||||
"OR ok2" => [
|
||||
"code" => str_replace("COND", "false ||false||true", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
|
||||
],
|
||||
"function_exists('ldap_connect')" => [
|
||||
"code" => str_replace("COND", "function_exists('ldap_connect')", $sTruePHP),
|
||||
"bool_expected" => function_exists('ldap_connect')
|
||||
|
||||
],
|
||||
"function_exists('gabuzomeushouldnotexist')" => [
|
||||
"code" => str_replace("COND", "function_exists('gabuzomeushouldnotexist')", $sTruePHP),
|
||||
"bool_expected" => function_exists('gabuzomeushouldnotexist')
|
||||
|
||||
],
|
||||
"1 > 2" => [
|
||||
"code" => str_replace("COND", "1 > 2", $sTruePHP),
|
||||
"bool_expected" => false
|
||||
|
||||
],
|
||||
"1 == 1" => [
|
||||
"code" => str_replace("COND", "1 == 1", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
|
||||
],
|
||||
"1 < 2" => [
|
||||
"code" => str_replace("COND", "1 < 2", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
],
|
||||
"PHP_VERSION_ID == PHP_VERSION_ID" => [
|
||||
"code" => str_replace("COND", "PHP_VERSION_ID == PHP_VERSION_ID", $sTruePHP),
|
||||
"bool_expected" => true
|
||||
],
|
||||
"PHP_VERSION_ID != PHP_VERSION_ID" => [
|
||||
"code" => str_replace("COND", "PHP_VERSION_ID != PHP_VERSION_ID", $sTruePHP),
|
||||
"bool_expected" => false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider EvaluateExpressionBooleanProvider
|
||||
*/
|
||||
public function testEvaluateExpressionBoolean($sPHP, $bExpected)
|
||||
{
|
||||
$aNodes = ModuleDiscoveryService::GetInstance()->parsePhpCode($sPHP);
|
||||
/** @var \PhpParser\Node\Expr $oExpr */
|
||||
$oExpr = $aNodes[0];
|
||||
$val = $this->InvokeNonPublicMethod(ModuleDiscoveryService::class, "EvaluateBooleanExpression", ModuleDiscoveryService::GetInstance(), [$oExpr->cond]);
|
||||
$this->assertEquals($bExpected, $val);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user