RegisterService('ModelReflection', new ModelReflectionRuntime()); $sProducedPHP = FormsCompiler::GetInstance()->CompileFormFromXML($sXMLContent); $this->AssertPHPCodeIsValid($sProducedPHP); $sMessage = $this->dataName(); $this->assertEquals($sExpectedPHP, $sProducedPHP, $sMessage); } public function CompileFormFromXMLProvider() { return [ 'Basic scalar properties should generate PHP' => [ 'sXMLContent' => << test XML, 'sExpectedPHP' => <<Add('title_property', 'Combodo\iTop\Forms\Block\DataModel\LabelFormBlock', [ 'label' => 'UI:BasicTest:Prop-Title', ]); \$this->Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [ 'label' => 'UI:BasicTest:Prop-Class', 'choices' => [ ], ]); } } PHP, ], 'Empty property tree should generate minimal PHP' => [ 'sXMLContent' => << XML, 'sExpectedPHP' => << [ 'sXMLContent' => << XML, 'sExpectedPHP' => << [ 'sXMLContent' => << test XML, 'sExpectedPHP' => <<Add('aggregate_function_property', 'Combodo\iTop\Forms\Block\DataModel\Dashlet\AggregateFunctionFormBlock', [ 'label' => 'UI:AggregateFunction', ]); \$this->Add('choice_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [ 'label' => 'UI:Choice', 'choices' => [ \Dict::S('Label A') => 'value_a', \Dict::S('Label B') => 'value_b', ], ]); \$this->Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [ 'label' => 'UI:Class', 'choices' => [ ], ]); \$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [ 'label' => 'UI:ClassAttribute', ]); \$this->Add('class_attribute_group_by_property', 'Combodo\iTop\Forms\Block\DataModel\Dashlet\ClassAttributeGroupByFormBlock', [ 'label' => 'UI:ClassAttributeGroupBy', ]); \$this->Add('class_attribute_value_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeValueChoiceFormBlock', [ 'label' => 'UI:ClassAttributeValue', ]); \$this->Add('integer_property', 'Combodo\iTop\Forms\Block\Base\IntegerFormBlock', [ 'label' => 'UI:Integer', ]); \$this->Add('label_property', 'Combodo\iTop\Forms\Block\DataModel\LabelFormBlock', [ 'label' => 'UI:Label', ]); \$this->Add('oql_property', 'Combodo\iTop\Forms\Block\DataModel\OqlFormBlock', [ 'label' => 'UI:OQL', ]); \$this->Add('string_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [ 'label' => 'UI:String', ]); \$this->Add('choice_from_input', 'Combodo\iTop\Forms\Block\Base\ChoiceFromInputsBlock', [ 'label' => 'UI:ChoiceFromInput', ]) ->AddInputDependsOn('value_a', 'class_attribute_property', 'label') ->AddInputDependsOn('value_b', 'class_attribute_group_by_property', 'label'); } } PHP, ], 'Collection of trees' => [ 'sXMLContent' => << UI:AddSubTree XML, 'sExpectedPHP' => <<Add('string_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [ 'label' => 'UI:String', ]); \$this->Add('integer_property', 'Combodo\iTop\Forms\Block\Base\IntegerFormBlock', [ 'label' => 'UI:Integer', ]); } } class FormFor__collection_of_trees_test extends Combodo\iTop\Forms\Block\Base\FormBlock { protected function BuildForm(): void { \$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', ]); } } PHP, ], 'Static inputs should be bound and invalid input should be ignored' => [ 'sXMLContent' => << Contact Test XML, 'sExpectedPHP' => <<Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [ 'label' => 'UI:ClassAttribute', ]) ->SetInputValue('class', 'Contact'); } } PHP, ], 'Quotes should be handled gracefully' => [ 'sXMLContent' => << {{CONCAT("'", '"')}} 'Class' and "Attribute" Test XML, 'sExpectedPHP' => <<Add('class_attribute_property_class_expression', 'Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock', [ 'expression' => 'CONCAT("\'", \'"\')', ]); \$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [ 'label' => '\'Class\' and "Attribute"', ]) ->InputDependsOn('class', 'class_attribute_property_class_expression', 'result') ->SetInputValue('category', '\'Class\' and "Attribute"'); } } PHP, ], 'Dynamic input should be bound' => [ 'sXMLContent' => << test {{class_property.text}} XML, 'sExpectedPHP' => <<Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [ 'label' => 'UI:Class', 'choices' => [ ], ]); \$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [ 'label' => 'UI:ClassAttribute', ]) ->InputDependsOn('class', 'class_property', 'text'); } } PHP, ], 'Dynamic input can be an expression' => [ 'sXMLContent' => << test {{IF(class_property.value = '', 'Person', class_property.value)}} XML, 'sExpectedPHP' => <<Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [ 'label' => 'UI:Class', 'choices' => [ ], ]); \$this->Add('class_attribute_property_class_expression', 'Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock', [ 'expression' => 'IF(class_property.value = \'\', \'Person\', class_property.value)', ]) ->AddInputDependsOn('class_property.value', 'class_property', 'value'); \$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [ 'label' => 'UI:ClassAttribute', ]) ->InputDependsOn('class', 'class_attribute_property_class_expression', 'result'); } } PHP, ], 'Relevance condition should generate a boolean block expression' => [ 'sXMLContent' => << {{source_property.text != 'count'}} XML, 'sExpectedPHP' => <<Add('source_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [ 'label' => 'UI:Source', ]); \$this->Add('dependant_property_visible_expression', 'Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock', [ 'expression' => 'source_property.text != \'count\'', ]) ->AddInputDependsOn('source_property.text', 'source_property', 'text'); \$this->Add('dependant_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [ 'label' => 'UI:Dependant', ]) ->InputDependsOn('visible', 'dependant_property_visible_expression', 'result'); } } PHP, ], 'Complex Relevance condition should generate a boolean block expression' => [ 'sXMLContent' => << {{IF(source_a_property.text != '', source_a_property.text, source_b_property.text)}} XML, 'sExpectedPHP' => <<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_visible_expression', '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_visible_expression', 'result'); } } PHP, ], 'Class category for value type class' => [ 'sXMLContent' => << addon/authentication,grant_by_profile,silo XML, 'sExpectedPHP' => <<Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [ 'label' => 'UI:Class', 'choices' => [ \Dict::S('Class:ActionEmail') => 'ActionEmail', \Dict::S('Class:ActionNewsroom') => 'ActionNewsroom', \Dict::S('Class:AuditCategory') => 'AuditCategory', \Dict::S('Class:AuditDomain') => 'AuditDomain', \Dict::S('Class:AuditRule') => 'AuditRule', \Dict::S('Class:OAuthClientAzure') => 'OAuthClientAzure', \Dict::S('Class:OAuthClientGoogle') => 'OAuthClientGoogle', \Dict::S('Class:QueryOQL') => 'QueryOQL', \Dict::S('Class:SynchroAttExtKey') => 'SynchroAttExtKey', \Dict::S('Class:SynchroAttLinkSet') => 'SynchroAttLinkSet', \Dict::S('Class:SynchroAttribute') => 'SynchroAttribute', \Dict::S('Class:SynchroDataSource') => 'SynchroDataSource', \Dict::S('Class:SynchroLog') => 'SynchroLog', \Dict::S('Class:SynchroReplica') => 'SynchroReplica', \Dict::S('Class:TriggerOnAttachmentCreate') => 'TriggerOnAttachmentCreate', \Dict::S('Class:TriggerOnAttachmentDelete') => 'TriggerOnAttachmentDelete', \Dict::S('Class:TriggerOnAttachmentDownload') => 'TriggerOnAttachmentDownload', \Dict::S('Class:TriggerOnAttributeBlobDownload') => 'TriggerOnAttributeBlobDownload', \Dict::S('Class:TriggerOnObjectCreate') => 'TriggerOnObjectCreate', \Dict::S('Class:TriggerOnObjectDelete') => 'TriggerOnObjectDelete', \Dict::S('Class:TriggerOnObjectMention') => 'TriggerOnObjectMention', \Dict::S('Class:TriggerOnObjectUpdate') => 'TriggerOnObjectUpdate', \Dict::S('Class:TriggerOnPortalUpdate') => 'TriggerOnPortalUpdate', \Dict::S('Class:TriggerOnStateEnter') => 'TriggerOnStateEnter', \Dict::S('Class:TriggerOnStateLeave') => 'TriggerOnStateLeave', \Dict::S('Class:TriggerOnThresholdReached') => 'TriggerOnThresholdReached', \Dict::S('Class:URP_Profiles') => 'URP_Profiles', \Dict::S('Class:URP_UserOrg') => 'URP_UserOrg', \Dict::S('Class:UserExternal') => 'UserExternal', \Dict::S('Class:UserLDAP') => 'UserLDAP', \Dict::S('Class:UserLocal') => 'UserLocal', ], ]); } } PHP, ], 'test' => [ 'sXMLContent' => << XML, 'sExpectedPHP' => <<expectException($sExpectedClass); $this->expectExceptionMessage($sExpectedMessage); FormsCompiler::GetInstance()->CompileFormFromXML($sXMLContent); } public function CompileFormFromInvalidXMLProvider() { return [ 'Invalid OQL expression in condition' => [ 'sXMLContent' => << {{source_property.text == 'count'}} XML, 'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException', 'sExpectedMessage' => 'Node: dependant_property, invalid syntax in condition: Unexpected token EQ - found \'=\' at 22 in \'source_property.text == \'count\'\'', ], 'Unknown source in relevance condition' => [ 'sXMLContent' => << {{source_property.text = 'count'}} XML, 'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException', 'sExpectedMessage' => 'Node: dependant_property, invalid source in condition: source_property', ], 'Unknown output in relevance condition' => [ 'sXMLContent' => << {{source_property.text_output != 'count'}} XML, 'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException', 'sExpectedMessage' => 'Node: dependant_property, invalid output in condition: source_property.text_output', ], 'Missing output or source in relevance condition' => [ 'sXMLContent' => << {{source_property != 'count'}} XML, 'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException', 'sExpectedMessage' => 'Node: dependant_property, missing output or source in condition: source_property', ], 'Missing value-type in node specification' => [ 'sXMLContent' => << XML, 'sExpectedClass' => 'Combodo\iTop\PropertyTree\PropertyTreeException', 'sExpectedMessage' => 'Node: source_property, missing value-type in node specification', ], ]; } }