mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-06 09:34:13 +01:00
N°8772 - Compiler: Add errors check
This commit is contained in:
@@ -24,5 +24,4 @@ class FormsException extends Exception
|
||||
parent::__construct($sMessage, $iCode, $oPrevious);
|
||||
IssueLog::Exception(get_class($this).' occurs: '.$sMessage, $this, null, $aContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ use utils;
|
||||
*/
|
||||
abstract class AbstractProperty
|
||||
{
|
||||
protected ?AbstractProperty $oParent;
|
||||
protected string $sId;
|
||||
protected ?string $sLabel;
|
||||
|
||||
/** @var array<AbstractProperty> */
|
||||
protected array $aChildren = [];
|
||||
protected ?AbstractValueType $oValueType;
|
||||
protected ?string $sParentId;
|
||||
protected ?string $sIdWithPath;
|
||||
|
||||
/**
|
||||
* Init property tree node from xml dom node
|
||||
@@ -34,14 +35,15 @@ abstract class AbstractProperty
|
||||
* @throws \DOMFormatException
|
||||
* @throws \Combodo\iTop\PropertyTree\PropertyTreeException
|
||||
*/
|
||||
public function InitFromDomNode(DesignElement $oDomNode, string $sParentId = ''): void
|
||||
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractProperty $oParent = null): void
|
||||
{
|
||||
if (utils::IsNotNullOrEmptyString($sParentId)) {
|
||||
$this->sId = $sParentId.'__';
|
||||
$this->oParent = $oParent;
|
||||
$this->sId = $oDomNode->getAttribute('id');
|
||||
if (is_null($oParent)) {
|
||||
$this->sIdWithPath = $this->sId;
|
||||
} else {
|
||||
$this->sId = '';
|
||||
$this->sIdWithPath = $oParent->sIdWithPath.'__'.$this->sId;
|
||||
}
|
||||
$this->sId .= $oDomNode->getAttribute('id');
|
||||
$this->sLabel = $oDomNode->GetChildText('label');
|
||||
}
|
||||
|
||||
@@ -61,4 +63,19 @@ abstract class AbstractProperty
|
||||
{
|
||||
return $this->aChildren;
|
||||
}
|
||||
|
||||
public function GetSibling(string $sId): ?AbstractProperty
|
||||
{
|
||||
if (is_null($this->oParent)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->oParent->GetChildren() as $oSibling) {
|
||||
if ($oSibling->sId == $sId) {
|
||||
return $oSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ class CollectionOfTrees extends AbstractProperty
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function InitFromDomNode(DesignElement $oDomNode, string $sParentId = ''): void
|
||||
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractProperty $oParent = null): void
|
||||
{
|
||||
parent::InitFromDomNode($oDomNode, $sParentId);
|
||||
parent::InitFromDomNode($oDomNode, $oParent);
|
||||
$oPropertyTreeFactory = PropertyTreeFactory::GetInstance();
|
||||
|
||||
$this->sButtonLabel = $oDomNode->GetChildText('button-label');
|
||||
@@ -30,7 +30,7 @@ class CollectionOfTrees extends AbstractProperty
|
||||
// read child properties
|
||||
foreach ($oDomNode->GetUniqueElement('prototype')->childNodes as $oNode) {
|
||||
if ($oNode instanceof DesignElement) {
|
||||
$this->AddChild($oPropertyTreeFactory->CreateNodeFromDom($oNode, $this->sId));
|
||||
$this->AddChild($oPropertyTreeFactory->CreateNodeFromDom($oNode, $this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class CollectionOfTrees extends AbstractProperty
|
||||
{
|
||||
$sFormBlockClass = CollectionBlock::class;
|
||||
|
||||
$sSubTreeClass = 'SubFormFor__'.$this->sId;
|
||||
$sSubTreeClass = 'SubFormFor__'.$this->sIdWithPath;
|
||||
|
||||
// Create the collection node
|
||||
$sLocalPHP = <<<PHP
|
||||
@@ -52,7 +52,7 @@ class CollectionOfTrees extends AbstractProperty
|
||||
PHP;
|
||||
|
||||
$sSubClassPHP = <<<PHP
|
||||
class SubFormFor__$this->sId extends Combodo\iTop\Forms\Block\Base\FormBlock
|
||||
class $sSubTreeClass extends Combodo\iTop\Forms\Block\Base\FormBlock
|
||||
{
|
||||
protected function BuildForm(): void
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Combodo\iTop\PropertyTree;
|
||||
|
||||
use Combodo\iTop\DesignElement;
|
||||
use Combodo\iTop\PropertyTree\ValueType\ValueTypeFactory;
|
||||
use Exception;
|
||||
use Expression;
|
||||
use utils;
|
||||
|
||||
@@ -22,9 +23,9 @@ class Property extends AbstractProperty
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function InitFromDomNode(DesignElement $oDomNode, string $sParentId = ''): void
|
||||
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractProperty $oParent = null): void
|
||||
{
|
||||
parent::InitFromDomNode($oDomNode);
|
||||
parent::InitFromDomNode($oDomNode, $oParent);
|
||||
|
||||
$oValueTypeNode = $oDomNode->GetOptionalElement('value-type');
|
||||
if ($oValueTypeNode) {
|
||||
@@ -34,6 +35,12 @@ class Property extends AbstractProperty
|
||||
$this->sRelevanceCondition = $oDomNode->GetChildText('relevance-condition');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aPHPFragments
|
||||
*
|
||||
* @return string
|
||||
* @throws \Combodo\iTop\PropertyTree\PropertyTreeException
|
||||
*/
|
||||
public function ToPHPFormBlock(&$aPHPFragments = []): string
|
||||
{
|
||||
$sFormBlockClass = $this->oValueType->GetFormBlockClass();
|
||||
@@ -42,12 +49,23 @@ class Property extends AbstractProperty
|
||||
$sRelevanceCondition = '';
|
||||
$sBinding = null;
|
||||
if (!is_null($this->sRelevanceCondition)) {
|
||||
$oExpression = Expression::FromOQL($this->sRelevanceCondition);
|
||||
$aFieldsToResolve = $oExpression->ListRequiredFields();
|
||||
try {
|
||||
$oExpression = Expression::FromOQL($this->sRelevanceCondition);
|
||||
} catch (Exception $e) {
|
||||
throw new PropertyTreeException("Node: {$this->sId}, invalid syntax in relevance condition: ".$e->getMessage());
|
||||
}
|
||||
$aFieldsToResolve = array_unique($oExpression->ListRequiredFields());
|
||||
foreach ($aFieldsToResolve as $sFieldToResolve) {
|
||||
if (preg_match('/(?<node>\w+)\.(?<output>\w+)/', $sFieldToResolve, $aMatches) === 1) {
|
||||
$sNode = $aMatches['node'];
|
||||
$oSibling = $this->GetSibling($sNode);
|
||||
if (is_null($oSibling)) {
|
||||
throw new PropertyTreeException("Node: {$this->sId}, invalid source in relevance condition: $sNode");
|
||||
}
|
||||
$sOutput = $aMatches['output'];
|
||||
if (!in_array($sOutput, $oSibling->oValueType->GetOutputs())) {
|
||||
throw new PropertyTreeException("Node: {$this->sId}, invalid output in relevance condition: $sFieldToResolve");
|
||||
}
|
||||
$sBinding .= "\n ->AddInputDependsOn('{$sNode}.$sOutput', '$sNode', '$sOutput')";
|
||||
} else {
|
||||
// TODO Erreur field sans alias
|
||||
|
||||
@@ -17,15 +17,15 @@ class PropertyTree extends AbstractProperty
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function InitFromDomNode(DesignElement $oDomNode, string $sParentId = ''): void
|
||||
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractProperty $oParent = null): void
|
||||
{
|
||||
parent::InitFromDomNode($oDomNode, $sParentId);
|
||||
parent::InitFromDomNode($oDomNode, $oParent);
|
||||
$oPropertyTreeFactory = PropertyTreeFactory::GetInstance();
|
||||
|
||||
// read child properties
|
||||
foreach ($oDomNode->GetUniqueElement('nodes')->childNodes as $oNode) {
|
||||
if ($oNode instanceof DesignElement) {
|
||||
$this->AddChild($oPropertyTreeFactory->CreateNodeFromDom($oNode, $this->sId));
|
||||
$this->AddChild($oPropertyTreeFactory->CreateNodeFromDom($oNode, $this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +38,14 @@ class PropertyTreeFactory
|
||||
* @throws \Combodo\iTop\PropertyTree\PropertyTreeException
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
public function CreateNodeFromDom(DesignElement $oDomNode, string $sParentId = ''): AbstractProperty
|
||||
public function CreateNodeFromDom(DesignElement $oDomNode, ?AbstractProperty $oParent = null): AbstractProperty
|
||||
{
|
||||
$sNodeType = $oDomNode->getAttribute('xsi:type');
|
||||
|
||||
// The class of the property tree node is given by the xsi:type attribute
|
||||
if (is_a($sNodeType, AbstractProperty::class, true)) {
|
||||
$oNode = new $sNodeType();
|
||||
$oNode->InitFromDomNode($oDomNode, $sParentId);
|
||||
$oNode->InitFromDomNode($oDomNode, $oParent);
|
||||
|
||||
return $oNode;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ abstract class AbstractValueType
|
||||
abstract public function GetFormBlockClass(): string;
|
||||
|
||||
protected array $aInputs = [];
|
||||
protected array $aOutputs = [];
|
||||
|
||||
public function InitFromDomNode(DesignElement $oDomNode): void
|
||||
{
|
||||
@@ -30,10 +31,18 @@ abstract class AbstractValueType
|
||||
$this->aInputs[$sInputName] = $sInputValue;
|
||||
}
|
||||
}
|
||||
foreach ($oBlockNode->GetOutputs() as $oOutput) {
|
||||
$this->aOutputs[] = $oOutput->GetName();
|
||||
}
|
||||
}
|
||||
|
||||
public function GetInputs(): array
|
||||
{
|
||||
return $this->aInputs;
|
||||
}
|
||||
|
||||
public function GetOutputs(): array
|
||||
{
|
||||
return $this->aOutputs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,6 @@
|
||||
<testsuite name="Webservices">
|
||||
<directory>unitary-tests/webservices</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Forms">
|
||||
<directory>unitary-tests/sources/Forms</directory>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="Extensions">
|
||||
<directory>../../env-production/*/test</directory>
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
|
||||
use Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock;
|
||||
use Combodo\iTop\Forms\Compiler\FormsCompiler;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
@@ -42,7 +40,7 @@ class TestFormsCompiler extends ItopDataTestCase
|
||||
<node id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
<nodes>
|
||||
<node id="title_property" xsi:type="Combodo-Property">
|
||||
<label>UI:BasicTest:Prop-Title2</label>
|
||||
<label>UI:BasicTest:Prop-Title</label>
|
||||
<value-type xsi:type="Combodo-ValueTypeLabel">
|
||||
</value-type>
|
||||
</node>
|
||||
@@ -255,7 +253,7 @@ class FormFor__collection_of_trees_test extends Combodo\iTop\Forms\Block\Base\Fo
|
||||
{
|
||||
protected function BuildForm(): void
|
||||
{
|
||||
\$this->Add('collection_of_trees_test__sub_tree_collection', 'Combodo\iTop\Forms\Block\Base\CollectionBlock', [
|
||||
\$this->Add('sub_tree_collection', 'Combodo\iTop\Forms\Block\Base\CollectionBlock', [
|
||||
'label' => 'UI:SubTree',
|
||||
'button_label' => 'UI:AddSubTree',
|
||||
'block_entry_type' => 'SubFormFor__collection_of_trees_test__sub_tree_collection',
|
||||
@@ -265,7 +263,7 @@ class FormFor__collection_of_trees_test extends Combodo\iTop\Forms\Block\Base\Fo
|
||||
PHP,
|
||||
],
|
||||
|
||||
'Input static' => [
|
||||
'Static inputs should be bound and invalid input should be ignored' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node id="input_static_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
@@ -294,7 +292,7 @@ class FormFor__input_static_test extends Combodo\iTop\Forms\Block\Base\FormBlock
|
||||
PHP,
|
||||
],
|
||||
|
||||
'Input binding' => [
|
||||
'Dynamic input should be bound' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node id="input_binding_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
@@ -331,7 +329,7 @@ class FormFor__input_binding_test extends Combodo\iTop\Forms\Block\Base\FormBloc
|
||||
PHP,
|
||||
],
|
||||
|
||||
'Relevance condition' => [
|
||||
'Relevance condition should generate a boolean block expression' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
@@ -373,6 +371,57 @@ class FormFor__RelevanceCondition extends Combodo\iTop\Forms\Block\Base\FormBloc
|
||||
PHP,
|
||||
],
|
||||
|
||||
'Complex Relevance condition should generate a boolean block expression' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node id="ComplexRelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
<nodes>
|
||||
<node id="source_a_property" xsi:type="Combodo-Property">
|
||||
<label>UI:Source</label>
|
||||
<value-type xsi:type="Combodo-ValueTypeString">
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="source_b_property" xsi:type="Combodo-Property">
|
||||
<label>UI:Source</label>
|
||||
<value-type xsi:type="Combodo-ValueTypeString">
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="dependant_property" xsi:type="Combodo-Property">
|
||||
<label>UI:Dependant</label>
|
||||
<relevance-condition>IF(source_a_property.text != '', source_a_property.text, source_b_property.text)</relevance-condition>
|
||||
<value-type xsi:type="Combodo-ValueTypeString">
|
||||
</value-type>
|
||||
</node>
|
||||
</nodes>
|
||||
</node>
|
||||
XML,
|
||||
'sExpectedPHP' => <<<PHP
|
||||
class FormFor__ComplexRelevanceCondition extends Combodo\iTop\Forms\Block\Base\FormBlock
|
||||
{
|
||||
protected function BuildForm(): void
|
||||
{
|
||||
\$this->Add('source_a_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
|
||||
'label' => 'UI:Source',
|
||||
]);
|
||||
|
||||
\$this->Add('source_b_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
|
||||
'label' => 'UI:Source',
|
||||
]);
|
||||
|
||||
\$this->Add('dependant_property_relevance_condition', 'Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock', [
|
||||
'expression' => "IF(source_a_property.text != '', source_a_property.text, source_b_property.text)",
|
||||
])
|
||||
->AddInputDependsOn('source_a_property.text', 'source_a_property', 'text')
|
||||
->AddInputDependsOn('source_b_property.text', 'source_b_property', 'text');
|
||||
|
||||
\$this->Add('dependant_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
|
||||
'label' => 'UI:Dependant',
|
||||
])
|
||||
->InputDependsOn('visible', 'dependant_property_relevance_condition', 'result');
|
||||
}
|
||||
}
|
||||
PHP,
|
||||
],
|
||||
'test' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -391,4 +440,86 @@ PHP,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider CompileFormFromInvalidXMLProvider
|
||||
* @param string $sXMLContent
|
||||
* @param string $sExpectedClass
|
||||
* @param string $sExpectedMessage
|
||||
*
|
||||
* @return void
|
||||
* @throws \Combodo\iTop\Forms\Compiler\FormsCompilerException
|
||||
* @throws \Combodo\iTop\PropertyTree\PropertyTreeException
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
public function testCompileFormFromInvalidXML(string $sXMLContent, string $sExpectedClass, string $sExpectedMessage)
|
||||
{
|
||||
$this->expectException($sExpectedClass);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
FormsCompiler::GetInstance()->CompileFormFromXML($sXMLContent);
|
||||
}
|
||||
|
||||
public function CompileFormFromInvalidXMLProvider()
|
||||
{
|
||||
return [
|
||||
'Invalid OQL expression in condition' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
<nodes>
|
||||
<node id="dependant_property" xsi:type="Combodo-Property">
|
||||
<label>UI:Dependant</label>
|
||||
<relevance-condition>source_property.text == 'count'</relevance-condition>
|
||||
<value-type xsi:type="Combodo-ValueTypeString">
|
||||
</value-type>
|
||||
</node>
|
||||
</nodes>
|
||||
</node>
|
||||
XML,
|
||||
'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException',
|
||||
'sExpectedMessage' => 'Node: dependant_property, invalid syntax in relevance condition: Unexpected token EQ - found \'=\' at 22 in \'source_property.text == \'count\'\'',
|
||||
],
|
||||
|
||||
'Unknown source in relevance condition' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
<nodes>
|
||||
<node id="dependant_property" xsi:type="Combodo-Property">
|
||||
<label>UI:Dependant</label>
|
||||
<relevance-condition>source_property.text = 'count'</relevance-condition>
|
||||
<value-type xsi:type="Combodo-ValueTypeString">
|
||||
</value-type>
|
||||
</node>
|
||||
</nodes>
|
||||
</node>
|
||||
XML,
|
||||
'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException',
|
||||
'sExpectedMessage' => 'Node: dependant_property, invalid source in relevance condition: source_property',
|
||||
],
|
||||
|
||||
'Unknown output in relevance condition' => [
|
||||
'sXMLContent' => <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyTree" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
|
||||
<nodes>
|
||||
<node id="source_property" xsi:type="Combodo-Property">
|
||||
<label>UI:Source</label>
|
||||
<value-type xsi:type="Combodo-ValueTypeString">
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="dependant_property" xsi:type="Combodo-Property">
|
||||
<label>UI:Dependant</label>
|
||||
<relevance-condition>source_property.text_output != 'count'</relevance-condition>
|
||||
<value-type xsi:type="Combodo-ValueTypeString">
|
||||
</value-type>
|
||||
</node>
|
||||
</nodes>
|
||||
</node>
|
||||
XML,
|
||||
'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException',
|
||||
'sExpectedMessage' => 'Node: dependant_property, invalid output in relevance condition: source_property.text_output',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user