N°8772 - dynamic form

This commit is contained in:
Benjamin Dalsass
2025-11-14 10:50:57 +01:00
parent 4d159ea3f1
commit e5058fb8f7
42 changed files with 880 additions and 540 deletions

View File

@@ -0,0 +1,258 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Symfony\Component\Form\FormEvents;
/**
*
*/
class AbstractFormIO
{
/** @var AbstractFormBlock The owner block */
private AbstractFormBlock $oOwnerBlock;
/** @var string Name of the IO */
private string $sName;
/** @var string Type of the IO data */
private string $sType;
/** @var array Stored values */
private array $aValues = [];
/** @var FormBinding|null */
private FormBinding|null $oBinding = null;
/** @var array bindings pointing to other inputs */
protected array $aBindingsToInputs = [];
/**
* Constructor.
*
* @param string $sName name of the IO
* @param string $sType type of the IO
*/
public function __construct(string $sName, string $sType, AbstractFormBlock $oOwnerBlock)
{
$this->sName = $sName;
$this->sType = $sType;
$this->oOwnerBlock = $oOwnerBlock;
}
/**
* Get the owner block.
*
* @return AbstractFormBlock
*/
public function GetOwnerBlock(): AbstractFormBlock
{
return $this->oOwnerBlock;
}
/**
* Get the IO name.
*
* @return string
*/
public function GetName(): string
{
return $this->sName;
}
/**
* Set the IO name.
*
* @param string $sName
*
* @return self
*/
public function SetName(string $sName): self
{
$this->sName = $sName;
return $this;
}
/**
* Get the IO data type.
*
* @return string
*/
public function GetDataType(): string
{
return $this->sType;
}
/**
* Set the IO value.
*
* @param string $sEventType
* @param mixed $oValue
*
* @return self
*/
public function SetValue(string $sEventType, mixed $oValue): self
{
$this->aValues[$sEventType] = $oValue;
return $this;
}
/**
* Get the IO value.
*
* @param string|null $sEventType
*
* @return mixed
*/
public function GetValue(string $sEventType = null): mixed
{
if($sEventType === null) {
return $this->Value();
}
return $this->aValues[$sEventType] ?? null;
}
/**
* Return true if value exist.
*
* @return bool
*/
public function HasValue(): bool
{
return $this->HasEventValue(FormEvents::POST_SET_DATA) || $this->HasEventValue(FormEvents::POST_SUBMIT);
}
/**
* Return true if value exist.
*
* @param string|null $sEventType
*
* @return bool
*/
public function HasEventValue(string $sEventType = null): bool
{
if($sEventType === null){
return $this->HasValue();
}
return array_key_exists($sEventType, $this->aValues) && $this->aValues[$sEventType] !== null;
}
/**
* Return all values.
*
* @return array
*/
public function GetValues(): array
{
return $this->aValues;
}
/**
* Set the IO values.
*
* @param array $aValues
*
* @return $this
*/
public function SetValues(array $aValues): self
{
$this->aValues = $aValues;
return $this;
}
/**
* Get the most relevant value.
*
* @return mixed
*/
private function Value(): mixed
{
if (array_key_exists(FormEvents::POST_SUBMIT, $this->aValues)) {
return $this->aValues[FormEvents::POST_SUBMIT];
}
if (array_key_exists(FormEvents::POST_SET_DATA, $this->aValues)) {
return $this->aValues[FormEvents::POST_SET_DATA];
}
return null;
}
/**
* Bind to input.
*
* @param FormInput $oDestinationIO
*
* @return FormBinding
*/
public function BindToInput(FormInput $oDestinationIO): FormBinding
{
$oBinding = new FormBinding($this, $oDestinationIO);
$this->aBindingsToInputs[] = $oBinding;
$oDestinationIO->Attach($oBinding);
return $oBinding;
}
/**
* Attach a binding.
*
* @param FormBinding $oFormBinding
*
* @return void
*/
public function Attach(FormBinding $oFormBinding)
{
$this->oBinding = $oFormBinding;
}
/**
* Indicate IO is bound.
*
* @return bool
*/
public function IsBound(): bool
{
return $this->oBinding !== null;
}
/**
* Return the binding.
*
* @return FormBinding|null
*/
public function GetBinding(): ?FormBinding
{
return $this->oBinding;
}
/**
* Indicated inputs data is ready.
*
* @return bool
*/
public function IsDataReady(): bool
{
return $this->HasValue();
}
public function HasBindingOut(): bool
{
return count($this->aBindingsToInputs) > 0;
}
public function GetBindingsToInputs(): array
{
return $this->aBindingsToInputs;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO\Converter;
/**
* Output converter.
*/
abstract class AbstractConverter
{
/**
* Convert the date to output format.
*
* @param mixed $oData
*
* @return mixed
*/
abstract public function Convert(mixed $oData): mixed;
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO\Converter;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\Forms\IO\Format\RawFormat;
/**
*
*/
class ChoiceValueToLabelConverter extends AbstractConverter
{
private ChoiceFormBlock $oChoiceBlock;
public function __construct(ChoiceFormBlock $oChoiceBlock)
{
$this->oChoiceBlock = $oChoiceBlock;
}
/** @inheritdoc */
public function Convert(mixed $oData): ?RawFormat
{
if(is_null($oData) || is_array($oData)){
return null;
}
$aOptions = array_flip($this->oChoiceBlock->GetOption('choices'));
return new RawFormat($aOptions[$oData]);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO\Converter;
use Combodo\iTop\Forms\IO\Format\ClassIOFormat;
use MetaModel;
use Symfony\Component\Filesystem\Exception\IOException;
/**
* OQL expression to class converter.
*/
class OqlToClassConverter extends AbstractConverter
{
/** @inheritdoc */
public function Convert(mixed $oData): ?ClassIOFormat
{
if ($oData === null) {
return null;
}
// Extract OQL information
preg_match('/SELECT\s+(\w+)/', $oData, $aMatches);
// Selected class
if (isset($aMatches[1])) {
$sSelectedClass = $aMatches[1];
if (!MetaModel::IsValidClass($sSelectedClass)) {
throw new IOException("Class `$sSelectedClass` not found");
}
return new ClassIOFormat($aMatches[1]);
} else {
throw new IOException('Incorrect OQL sentence');
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO\Converter;
use Combodo\iTop\Forms\IO\Format\AttributeIOFormat;
/**
* String to attribute converter.
*/
class StringToAttributeConverter extends AbstractConverter
{
/** @inheritdoc */
public function Convert(mixed $oData): ?AttributeIOFormat
{
if ($oData === null) {
return null;
}
return new AttributeIOFormat($oData);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO\Converter;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
/**
* OQL expression to class converter.
*/
class StringToBooleanConverter extends AbstractConverter
{
/** @inheritdoc */
public function Convert(mixed $oData): ?BooleanIOFormat
{
return new BooleanIOFormat($oData );
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO;
/**
*
*/
class FormBinding
{
public readonly AbstractFormIO $oSourceIO;
public readonly AbstractFormIO $oDestinationIO;
/**
* @param AbstractFormIO $oSourceIO
* @param AbstractFormIO $oDestinationIO
*/
public function __construct(AbstractFormIO $oSourceIO, AbstractFormIO $oDestinationIO)
{
$this->oDestinationIO = $oDestinationIO;
$this->oSourceIO = $oSourceIO;
}
/**
* Propagate binding values.
*
* @return void
*/
public function PropagateValues(): void
{
$this->oDestinationIO->SetValues($this->oSourceIO->GetValues());
$this->oDestinationIO->GetOwnerBlock()->BindingReceivedEvent($this->oDestinationIO);
}
}

View File

@@ -0,0 +1,14 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO;
use Combodo\iTop\Forms\FormsException;
class FormBlockIOException extends FormsException
{
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO;
/**
*
*/
class FormInput extends AbstractFormIO
{
/**
* @return bool
*/
public function IsDataReady(): bool
{
return $this->HasValue();
}
/**
* @param string|null $sEventType
*
* @return bool
*/
public function IsEventDataReady(string $sEventType = null): bool
{
return $this->HasEventValue($sEventType);
}
/**
* Set the values of the input.
*
* @param array $aValues
*
* @return AbstractFormIO
*/
public function SetValues(array $aValues): AbstractFormIO
{
parent::SetValues($aValues);
$this->PropagateBindingsValues();
return $this;
}
/**
* Propagate the bindings values.
*
* @return void
*/
public function PropagateBindingsValues(): void
{
// propagate the value
foreach ($this->aBindingsToInputs as $oBinding) {
$oBinding->PropagateValues();
}
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\IO;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\IO\Converter\AbstractConverter;
/**
*
*/
class FormOutput extends AbstractFormIO
{
/** @var AbstractConverter|null */
private null|AbstractConverter $oConverter;
/** @var array */
private array $aBindingsToOutputs = [];
/**
* Constructor.
*
* @param string $sName
* @param string $sType
* @param AbstractConverter|null $oConverter
*/
public function __construct(string $sName, string $sType, AbstractFormBlock $oOwnerBlock, AbstractConverter $oConverter = null)
{
parent::__construct($sName, $sType, $oOwnerBlock);
$this->oConverter = $oConverter;
}
/**
* Convert the value.
*
* @param mixed $oData
*
* @return mixed
*/
public function ConvertValue(mixed $oData): mixed
{
if (is_null($this->oConverter)) {
return $oData;
}
return $this->oConverter->Convert($oData);
}
/**
* Compute the value.
*
* @param string $sEventType
* @param mixed $oData
*
* @return void
*/
public function ComputeValue(string $sEventType, mixed $oData): void
{
$this->SetValue($sEventType, $this->ConvertValue($oData));
}
/**
* Propagate the bindings values.
*
* @return void
*/
public function PropagateBindingsValues(): void
{
// propagate the value
foreach ($this->aBindingsToInputs as $oBinding) {
$oBinding->PropagateValues();
}
// propagate the value
foreach ($this->aBindingsToOutputs as $oBinding) {
$oBinding->PropagateValues();
}
}
/**
* Bind to output.
*
* @param FormOutput $oDestinationIO
*
* @return FormBinding
*/
public function BindToOutput(FormOutput $oDestinationIO): FormBinding
{
$oBinding = new FormBinding($this, $oDestinationIO);
$this->aBindingsToOutputs[] = $oBinding;
$oDestinationIO->Attach($oBinding);
return $oBinding;
}
/**
* Get the bindings.
*
* @return array
*/
public function GetBindings(): array
{
return $this->aBindingsToInputs;
}
public function HasBindings(): bool
{
return count($this->aBindingsToInputs) > 0;
}
public function SetValue(string $sEventType, mixed $oValue): AbstractFormIO
{
parent::SetValue($sEventType, $oValue);
// propagate the bindings values
$this->PropagateBindingsValues();
return $this;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Combodo\iTop\Forms\IO\Format;
use JsonSerializable;
class AttributeIOFormat implements JsonSerializable
{
public string $sAttributeName;
public function __construct(string $sAttributeName)
{
$this->sAttributeName = $sAttributeName;
// validation du format sinon exception
}
public function __toString(): string
{
return $this->sAttributeName;
}
public function jsonSerialize(): mixed
{
return $this->sAttributeName;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Combodo\iTop\Forms\IO\Format;
use JsonSerializable;
class BooleanIOFormat implements JsonSerializable
{
public function __construct(public bool $bValue)
{
}
public function IsTrue(): bool
{
return $this->bValue;
}
public function __toString(): string
{
return $this->bValue ? 'true' : 'false';
}
public function jsonSerialize(): mixed
{
return $this->bValue;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Combodo\iTop\Forms\IO\Format;
use JsonSerializable;
class ClassIOFormat implements JsonSerializable
{
public string $sClassName;
public function __construct(string $sClassName)
{
$this->sClassName = $sClassName;
// validation du format sinon exception
}
public function __toString(): string
{
return $this->sClassName;
}
public function jsonSerialize(): mixed
{
return $this->sClassName;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Combodo\iTop\Forms\IO\Format;
class NumberIOFormat
{
public mixed $oValue;
public function __construct(string $oValue)
{
$this->oValue = $oValue;
// validation du format sinon exception
}
public function __toString(): string
{
return $this->oValue;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Combodo\iTop\Forms\IO\Format;
class RawFormat
{
public string $sValue;
public function __construct(string $sValue)
{
$this->sValue = $sValue;
// validation du format sinon exception
}
public function __toString(): string
{
return strval($this->sValue);
}
}