diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 0b2f0d93f..2b0d9e97a 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -491,6 +491,7 @@ return array( 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/OqlFormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Expression\\ExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/ExpressionFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockException' => $baseDir . '/sources/Forms/Block/FormBlockException.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockIOException' => $baseDir . '/sources/Forms/Block/IO/FormBlockIOException.php', 'Combodo\\iTop\\Forms\\Block\\FormType\\ChoiceFormType' => $baseDir . '/sources/Forms/Block/FormType/ChoiceFormType.php', @@ -523,6 +524,7 @@ return array( 'Combodo\\iTop\\Forms\\FormsException' => $baseDir . '/sources/Forms/FormsException.php', 'Combodo\\iTop\\Forms\\IFormBlock' => $baseDir . '/sources/Forms/IFormBlock.php', 'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => $baseDir . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php', + 'Combodo\\iTop\\ItopSdkFormDemonstrator\\Form\\Block\\ExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Base/ExpressionFormBlock.php', 'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => $baseDir . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => $baseDir . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index ef80b7de1..f450c1e58 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -872,6 +872,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/OqlFormBlock.php', + 'Combodo\\iTop\\Forms\\Block\\Expression\\ExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/ExpressionFormBlock.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockException' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockException.php', 'Combodo\\iTop\\Forms\\Block\\FormBlockIOException' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormBlockIOException.php', 'Combodo\\iTop\\Forms\\Block\\FormType\\ChoiceFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/ChoiceFormType.php', @@ -904,6 +905,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Forms\\FormsException' => __DIR__ . '/../..' . '/sources/Forms/FormsException.php', 'Combodo\\iTop\\Forms\\IFormBlock' => __DIR__ . '/../..' . '/sources/Forms/IFormBlock.php', 'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => __DIR__ . '/../..' . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php', + 'Combodo\\iTop\\ItopSdkFormDemonstrator\\Form\\Block\\ExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/ExpressionFormBlock.php', 'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => __DIR__ . '/../..' . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php', diff --git a/sources/Forms/Block/Base/CheckboxFormBlock.php b/sources/Forms/Block/Base/CheckboxFormBlock.php index 3417ad9d1..a64a00f40 100644 --- a/sources/Forms/Block/Base/CheckboxFormBlock.php +++ b/sources/Forms/Block/Base/CheckboxFormBlock.php @@ -26,6 +26,7 @@ class CheckboxFormBlock extends AbstractTypeFormBlock return CheckboxType::class; } + /** @inheritdoc */ function InitBlockOptions(array &$aUserOptions): void { parent::InitBlockOptions($aUserOptions); diff --git a/sources/Forms/Block/Base/ExpressionFormBlock.php b/sources/Forms/Block/Base/ExpressionFormBlock.php new file mode 100644 index 000000000..1401caf68 --- /dev/null +++ b/sources/Forms/Block/Base/ExpressionFormBlock.php @@ -0,0 +1,96 @@ +AddOutput(self::OUTPUT_RESULT, BooleanIOFormat::class); +// $this->AddOutput(self::OUTPUT_RAW, RawFormat::class); + } + +// public function InputHasChanged() +// { +// if (!$this->IsInputsDataReady()) { +// return; +// } +// $sExpression = $this->GetOptions()['expression']; +// $sValue = preg_replace_callback( +// "/\[\[(?[^\]]+)]]/", +// function(array $aMatches): string { +// return $this->GetInput($aMatches['input'])->GetValue(); +// }, +// $sExpression); +// +// foreach ($this->GetInputs() as $oFormInput) { +// IssueLog::Info($oFormInput->GetName().' = '.$oFormInput->GetValue()); +// } +// IssueLog::Info("Result of [$sExpression] is [$sValue]"); +// +// $result = ''; +// eval('$result = '.$sValue.';'); +// IssueLog::Info("Result of [$sExpression] is eval to [$result]"); +// +// $this->GetOutput(self::OUTPUT_RESULT)->SetValue(FormEvents::POST_SUBMIT, new BooleanIOFormat($result)); +// $this->GetOutput(self::OUTPUT_VALUE)->SetValue(FormEvents::POST_SUBMIT, new RawFormat($result)); +// } + + + + public function InputHasChanged() + { + if (!$this->IsInputsDataReady()) { + return; + } + $sExpression = $this->GetOptions()['expression']; + + $this->Compute($sExpression, FormEvents::POST_SET_DATA); + $this->Compute($sExpression, FormEvents::POST_SUBMIT); + } + + public function Compute(string $sExpression, string $sEventType): void + { + try{ + $sValue = preg_replace_callback( + "/\[\[(?[^\]]+)]]/", + function(array $aMatches) use ($sEventType): ?string { + $oInput = $this->GetInput($aMatches['input']); + if(!$oInput->HasEventValue($sEventType)){ + throw new FormsException('Unable to compute expression: input '.$aMatches['input'].' has no value for event '.$sEventType.'.'); + } + return $oInput->GetValue($sEventType); + }, + $sExpression); + + $result = ''; + eval('$result = '.$sValue.';'); + + $this->GetOutput(self::OUTPUT_RESULT)->SetValue($sEventType, new BooleanIOFormat($result)); + $this->GetOutput(self::OUTPUT_VALUE)->SetValue($sEventType, new RawFormat($result)); + } + catch(\Exception $e){ + IssueLog::Exception('Compute expression block issue', $e); + } + } + +} \ No newline at end of file diff --git a/sources/Forms/Block/Expression/ExpressionFormBlock.php b/sources/Forms/Block/Expression/ExpressionFormBlock.php new file mode 100644 index 000000000..8eea4961b --- /dev/null +++ b/sources/Forms/Block/Expression/ExpressionFormBlock.php @@ -0,0 +1,96 @@ +AddOutput(self::OUTPUT_RESULT, BooleanIOFormat::class); +// $this->AddOutput(self::OUTPUT_RAW, RawFormat::class); + } + +// public function InputHasChanged() +// { +// if (!$this->IsInputsDataReady()) { +// return; +// } +// $sExpression = $this->GetOptions()['expression']; +// $sValue = preg_replace_callback( +// "/\[\[(?[^\]]+)]]/", +// function(array $aMatches): string { +// return $this->GetInput($aMatches['input'])->GetValue(); +// }, +// $sExpression); +// +// foreach ($this->GetInputs() as $oFormInput) { +// IssueLog::Info($oFormInput->GetName().' = '.$oFormInput->GetValue()); +// } +// IssueLog::Info("Result of [$sExpression] is [$sValue]"); +// +// $result = ''; +// eval('$result = '.$sValue.';'); +// IssueLog::Info("Result of [$sExpression] is eval to [$result]"); +// +// $this->GetOutput(self::OUTPUT_RESULT)->SetValue(FormEvents::POST_SUBMIT, new BooleanIOFormat($result)); +// $this->GetOutput(self::OUTPUT_VALUE)->SetValue(FormEvents::POST_SUBMIT, new RawFormat($result)); +// } + + + + public function InputHasChanged() + { + if (!$this->IsInputsDataReady()) { + return; + } + $sExpression = $this->GetOptions()['expression']; + + $this->Compute($sExpression, FormEvents::POST_SET_DATA); + $this->Compute($sExpression, FormEvents::POST_SUBMIT); + } + + public function Compute(string $sExpression, string $sEventType): void + { + try{ + $sValue = preg_replace_callback( + "/\[\[(?[^\]]+)]]/", + function(array $aMatches) use ($sEventType): ?string { + $oInput = $this->GetInput($aMatches['input']); + if(!$oInput->HasEventValue($sEventType)){ + throw new FormsException('Unable to compute expression: input '.$aMatches['input'].' has no value for event '.$sEventType.'.'); + } + return $oInput->GetValue($sEventType); + }, + $sExpression); + + $result = ''; + eval('$result = '.$sValue.';'); + + $this->GetOutput(self::OUTPUT_RESULT)->SetValue($sEventType, new BooleanIOFormat($result)); + $this->GetOutput(self::OUTPUT_VALUE)->SetValue($sEventType, new RawFormat($result)); + } + catch(\Exception $e){ + IssueLog::Exception('Compute expression block issue', $e); + } + } + +} \ No newline at end of file diff --git a/sources/Forms/Block/IO/AbstractFormIO.php b/sources/Forms/Block/IO/AbstractFormIO.php index 1a82b3c55..f2e928876 100644 --- a/sources/Forms/Block/IO/AbstractFormIO.php +++ b/sources/Forms/Block/IO/AbstractFormIO.php @@ -245,5 +245,14 @@ class AbstractFormIO return $this->HasValue(); } + public function HasBindingOut(): bool + { + return count($this->aBindingsToInputs) > 0; + } + + public function GetBindingsToInputs(): array + { + return $this->aBindingsToInputs; + } } \ No newline at end of file diff --git a/sources/Forms/FormBuilder/DependencyHandler.php b/sources/Forms/FormBuilder/DependencyHandler.php index 499afd7b7..782387a33 100644 --- a/sources/Forms/FormBuilder/DependencyHandler.php +++ b/sources/Forms/FormBuilder/DependencyHandler.php @@ -26,8 +26,8 @@ class DependencyHandler /** @var DependencyMap dependencies map */ private DependencyMap $oDependenciesMap; - /** @var array Debug data */ - private array $aDebugData = []; + /** @var array events */ + private array $aEvents = []; private readonly string $sName; private readonly AbstractFormBlock $oFormBlock; private readonly FormBuilder $oFormBuilder; @@ -94,12 +94,8 @@ class DependencyHandler continue; } - $this->aDebugData[] = [ - 'builder' => $this->oFormBuilder->getName(), - 'event' => 'form.listen', - 'form' => $sOutputBlockName, - 'value' => 'NA', - ]; + // Add event + $this->AddEvent('form.listen', $sOutputBlockName); // Listen the output block POST_SET_DATA & POST_SUBMIT $this->oFormBuilder->get($sOutputBlockName)->addEventListener(FormEvents::POST_SET_DATA, $this->GetEventListeningCallback()); @@ -120,12 +116,8 @@ class DependencyHandler // Get the event type $sEventType = FormHelper::GetEventType($oEvent); - $this->aDebugData[] = [ - 'builder' => $this->oFormBuilder->getName(), - 'event' => $sEventType, - 'form' => $oEvent->getForm()->getName(), - 'value' => $oEvent->getData(), - ]; + // Add event + $this->AddEvent($sEventType, $oEvent->getForm()->getName(), $oEvent->getData()); // Get the form $oForm = $oEvent->getForm(); @@ -176,9 +168,13 @@ class DependencyHandler if ($oDependentBlock->IsVisible($sEventType) && $oDependentBlock->IsInputsDataReady($sEventType)) { // Get the dependent field options + $aBefore = $oDependentBlock->GetOptionsMergedWithDynamic(); $oDependentBlock->UpdateDynamicOptions($sEventType); $aOptions = $oDependentBlock->GetOptionsMergedWithDynamic($sEventType); + // Options changed flag + $bOptionsChanged = FormHelper::CompareArrayValues($aBefore, $aOptions); + // Add the listener callback to the dependent field if it is also a dependency for another field if ($this->oDependenciesMap->IsTheBlockInDependencies($oDependentBlock->getName())) { @@ -188,22 +184,12 @@ class DependencyHandler ]); } - if ($oDependentBlock->AllowAdd($sEventType)) { - - $this->aDebugData[] = [ - 'builder' => $this->oFormBuilder->getName(), - 'event' => 'form.add', - 'form' => $oDependentBlock->getName(), - 'value' => 'NA', - ]; + if ( (!$oDependentBlock->IsAdded() || $bOptionsChanged) && $oDependentBlock->AllowAdd($sEventType)) { + // Add events + $this->AddEvent('form.add', $oDependentBlock->getName()); if (array_key_exists('builder_listener', $aOptions)) { - $this->aDebugData[] = [ - 'builder' => $this->oFormBuilder->getName(), - 'event' => 'form.listen.after', - 'form' => $oDependentBlock->getName(), - 'value' => 'NA', - ]; + $this->AddEvent('form.listen.after', $oDependentBlock->getName()); } // Mark the dependency as added @@ -220,12 +206,8 @@ class DependencyHandler $oForm->remove($oDependentBlock->GetName()); $oDependentBlock->SetAdded(false); - $this->aDebugData[] = [ - 'builder' => $this->oFormBuilder->getName(), - 'event' => 'form.remove', - 'form' => $oDependentBlock->getName(), - 'value' => 'NA' - ]; + // Add event + $this->AddEvent('form.remove', $oDependentBlock->getName()); } } @@ -239,7 +221,7 @@ class DependencyHandler */ public function GetDebugData(): array { - return $this->aDebugData; + return $this->aEvents; } public function GetMap(): DependencyMap @@ -247,13 +229,20 @@ class DependencyHandler return $this->oDependenciesMap; } - public function GetSubBlocks(): array - { - return $this->aSubBlocks; - } public function GetName(): string { return $this->sName; } + + private function AddEvent(string $sEvent, string $sForm, mixed $oValue = 'NA'): void + { + + $this->aEvents[] = [ + 'builder' => $this->oFormBuilder->getName(), + 'event' => $sEvent, + 'form' => $sForm, + 'value' => $oValue, + ]; + } } \ No newline at end of file diff --git a/sources/Forms/FormBuilder/DependencyMap.php b/sources/Forms/FormBuilder/DependencyMap.php index c3bb37eec..f88d218d7 100644 --- a/sources/Forms/FormBuilder/DependencyMap.php +++ b/sources/Forms/FormBuilder/DependencyMap.php @@ -23,6 +23,9 @@ class DependencyMap /** @var array array of blocks with dependencies group by dependence */ private array $aBlocksWithDependenciesGroupByDependence = []; + /** @var array array of binding */ + private array $aBindings = []; + /** @var array array of binding (OUT > OUT) grouped by block and output name */ private array $aBindingsOutputToInput = []; @@ -148,6 +151,7 @@ class DependencyMap // add to map $map[$sBlockName][$sIOName][] = $oBinding; + $this->aBindings[] = $oBinding; } /** @@ -240,4 +244,9 @@ class DependencyMap { return $this->aBindingsOutputToOutputs; } + + public function GetBindings() + { + return $this->aBindings; + } } \ No newline at end of file diff --git a/sources/Forms/FormBuilder/FormHelper.php b/sources/Forms/FormBuilder/FormHelper.php index 276da8e10..863b23820 100644 --- a/sources/Forms/FormBuilder/FormHelper.php +++ b/sources/Forms/FormBuilder/FormHelper.php @@ -30,4 +30,22 @@ class FormHelper throw new FormBuilderException(sprintf('Unknown event type %s', get_class($event))); } + + public static function CompareArrayValues($mValue1, $mValue2): int + { + if (is_array($mValue1) && is_array($mValue2)) { + if (count($mValue1) !== count($mValue2)) { + return 1; + } + $aDiff = array_udiff_assoc($mValue1, $mValue2, [FormHelper::class, 'CompareArrayValues']); + + return count($aDiff); + } + + if ($mValue1 === $mValue2) { + return 0; + } + + return 1; + } } \ No newline at end of file