N°8772 - Form dependencies manager implementation

- Form SDK implementation
- Basic Forms
- Dynamics Forms
- Basic Blocks + Data Model Block
- Form Compilation
- Turbo integration
This commit is contained in:
Benjamin Dalsass
2025-12-30 11:42:55 +01:00
committed by GitHub
parent 3955b4eb22
commit 4c1ad0f4f2
813 changed files with 115243 additions and 489 deletions

View File

@@ -0,0 +1,47 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
/**
* A block to manage a checkbox.
* This block expose one output: whether the checkbox is checked or not.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class CheckboxFormBlock extends AbstractTypeFormBlock
{
// outputs
public const OUTPUT_CHECKED = 'checked';
/** @inheritdoc */
public function GetFormType(): string
{
return CheckboxType::class;
}
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('required', false);
}
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_CHECKED, BooleanIOFormat::class);
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\FormType\Base\ChoiceFormType;
use Combodo\iTop\Forms\IO\Converter\ChoiceValueToLabelConverter;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Forms\Register\OptionsRegister;
/**
* A block to manage a list of choices.
* This block expose two outputs: the label and the value of the selected choice.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class ChoiceFormBlock extends AbstractTypeFormBlock
{
// Outputs
public const OUTPUT_LABEL = 'label';
public const OUTPUT_VALUE = 'value';
/** @inheritdoc */
public function GetFormType(): string
{
return ChoiceFormType::class;
}
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('multiple', false);
}
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
parent::RegisterIO($oIORegister);
$bMultiple = $this->GetOption('multiple');
$oIORegister->AddOutput(self::OUTPUT_LABEL, StringIOFormat::class, $bMultiple, new ChoiceValueToLabelConverter($this));
$oIORegister->AddOutput(self::OUTPUT_VALUE, StringIOFormat::class, $bMultiple);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\IO\FormInput;
use Combodo\iTop\Forms\Register\OptionsRegister;
/**
* A block to manage a list of choices given by forms inputs current values.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class ChoiceFromInputsBlock extends ChoiceFormBlock
{
/** @inheritdoc */
public function UpdateOptions(OptionsRegister $oOptionsRegister): void
{
parent::UpdateOptions($oOptionsRegister);
// Compute options based on inputs values
$aChoices = [];
/** @var FormInput $oInput */
foreach ($this->GetInputs() as $oInput) {
if ($oInput->HasValue()) {
$aChoices[strval($oInput->GetValue())] = $oInput->GetName();
}
}
$oOptionsRegister->SetOption('choices', $aChoices);
}
}

View File

@@ -0,0 +1,92 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\FormType\Base\CollectionFormType;
use Combodo\iTop\Forms\IO\Converter\CollectionToCountConverter;
use Combodo\iTop\Forms\IO\Format\IntegerIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Combodo\iTop\Forms\Register\RegisterException;
/**
* A block to manage collections of form blocks.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class CollectionBlock extends AbstractTypeFormBlock
{
/** @var AbstractTypeFormBlock Prototype block */
private AbstractTypeFormBlock $oPrototypeBlock;
public const OUTPUT_COUNT = 'count';
/** @inheritdoc */
public function GetFormType(): string
{
return CollectionFormType::class;
}
/**
* @return AbstractFormBlock
*/
public function GetPrototypeBlock(): AbstractFormBlock
{
return $this->oPrototypeBlock;
}
public function EntryDependsOnParent(string $sInputName, string $sParentInputName): AbstractFormBlock
{
$this->oPrototypeBlock->InputDependsOnParent($sInputName, $sParentInputName);
return $this;
}
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_COUNT, IntegerIOFormat::class, false, new CollectionToCountConverter());
}
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('block_entry_type', FormBlock::class, false);
$oOptionsRegister->SetOption('block_entry_options', [], false);
$oOptionsRegister->SetOption('prototype', true);
$oOptionsRegister->SetOption('allow_add', true);
$oOptionsRegister->SetOption('prototype_options', [
'label' => false,
]);
}
/** @inheritdoc */
protected function AfterOptionsRegistered(OptionsRegister $oOptionsRegister): void
{
parent::AfterOptionsRegistered($oOptionsRegister);
$oBlockEntryType = $this->GetOption('block_entry_type');
$oBlockEntryOptions = $this->GetOption('block_entry_options');
$this->oPrototypeBlock = new $oBlockEntryType('prototype', array_merge($this->GetOption('prototype_options'), $oBlockEntryOptions));
$this->oPrototypeBlock->SetParent($this);
try {
$oOptionsRegister->SetOption('entry_type', $this->oPrototypeBlock->GetFormType());
$oOptionsRegister->SetOption('entry_options', $this->oPrototypeBlock->GetOptions());
} catch (RegisterException $e) {
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Symfony\Component\Form\Extension\Core\Type\DateType;
/**
* A block to manage a date field.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class DateFormBlock extends AbstractTypeFormBlock
{
/** @inheritdoc */
public function GetFormType(): string
{
return DateType::class;
}
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('widget', 'single_text');
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
/**
* A block to manage a date and time field
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class DateTimeFormBlock extends AbstractTypeFormBlock
{
/** @inheritdoc */
public function GetFormType(): string
{
return DateTimeType::class;
}
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('widget', 'single_text');
}
}

View File

@@ -0,0 +1,172 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\FormBuilder\DependencyMap;
use Combodo\iTop\Forms\FormsException;
use Combodo\iTop\Forms\FormType\Base\FormType;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Exception;
use ReflectionClass;
use ReflectionException;
/**
* A block to manage a form with children.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class FormBlock extends AbstractTypeFormBlock
{
/** @var AbstractFormBlock[] children blocks */
private array $aChildrenBlocks = [];
public ?DependencyMap $oDependencyMap = null;
/**
* Constructor.
*
* @param string $sName block name
* @param array $aOptions options
*
* @throws FormsException
*/
public function __construct(string $sName, array $aOptions = [])
{
parent::__construct($sName, $aOptions);
try {
// Build the form
$this->BuildForm();
} catch (FormsException $e) {
throw $e;
} catch (Exception $e) {
throw new FormBlockException('Unable to construct form', 0, $e);
}
}
/** @inheritdoc */
public function GetFormType(): string
{
return FormType::class;
}
/** @inheritdoc */
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
{
parent::RegisterOptions($oOptionsRegister);
$oOptionsRegister->SetOption('compound', true);
$oOptionsRegister->SetOptionArrayValue('attr', 'class', 'form');
}
/**
* Add a child form.
*
* @param string $sName block name
* @param string $sBlockClass block class name
* @param array $aOptions options
*
* @return $this
* @throws ReflectionException
* @throws FormBlockException
*/
public function Add(string $sName, string $sBlockClass, array $aOptions = []): AbstractFormBlock
{
$this->VerifyBlockName($sName);
$this->VerifyBlockClassName($sBlockClass);
$aOptions['priority'] = -count($this->aChildrenBlocks);
$oSubFormBlock = new ($sBlockClass)($sName, $aOptions);
$this->aChildrenBlocks[$sName] = $oSubFormBlock;
$oSubFormBlock->SetParent($this);
return $oSubFormBlock;
}
/**
* @param string $sBlockName
*
* @return void
* @throws FormBlockException
*/
private function VerifyBlockName(string $sBlockName): void
{
if (!ctype_alnum(str_replace(['-', '_'], '', $sBlockName))) {
throw new FormBlockException("Block name '$sBlockName' is not valid. Only alphanumeric characters, hyphens and underscores are allowed.");
}
}
/**
* @param string $sBlockClass
*
* @return void
* @throws FormBlockException
* @throws ReflectionException
*/
private function VerifyBlockClassName(string $sBlockClass): void
{
if (!is_a($sBlockClass, AbstractFormBlock::class, true)) {
throw new FormBlockException('The block type '.json_encode($sBlockClass).' is not a subclass of '.json_encode(AbstractFormBlock::class));
}
}
/**
* Get the children forms.
*
* @return array
*/
public function GetChildren(): array
{
return $this->aChildrenBlocks;
}
/**
* Return a child block.
*
* @param string $sName name of the block
*
* @return AbstractFormBlock
* @throws \Combodo\iTop\Forms\Block\FormBlockException
*/
public function Get(string $sName): AbstractFormBlock
{
if (!array_key_exists($sName, $this->aChildrenBlocks)) {
throw new FormBlockException('Block does not exist '.json_encode($sName));
}
return $this->aChildrenBlocks[$sName];
}
/**
* Build the form.
*
* @return void
*/
protected function BuildForm(): void
{
}
public function GetSubFormBlock(string $sBlockTurboTriggerName): ?AbstractFormBlock
{
$oBlock = $this;
if (preg_match_all('/\[(?<level>[^\[]+)\]/', $sBlockTurboTriggerName, $aMatches)) {
foreach ($aMatches['level'] as $level) {
$oBlock = $oBlock->Get($level);
}
}
return $oBlock;
}
public function GetDependenciesMap(): ?DependencyMap
{
return $this->oDependencyMap;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
/**
* A block to manage a hidden field
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class HiddenFormBlock extends AbstractTypeFormBlock
{
/**
* @inheritDoc
*/
public function GetFormType(): string
{
return HiddenType::class;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\IO\Format\IntegerIOFormat;
use Combodo\iTop\Forms\IO\Format\NumberIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
/**
* A block to manage an integer
* This block exposes a single output: the integer value.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class IntegerFormBlock extends AbstractTypeFormBlock
{
public const OUTPUT_INTEGER = 'integer';
public function GetFormType(): string
{
return IntegerType::class;
}
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_INTEGER, IntegerIOFormat::class);
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\IO\Format\NumberIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
/**
* A block to manage a number input.
* This block exposes a single output: the number value.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class NumberFormBlock extends AbstractTypeFormBlock
{
// Outputs
public const OUTPUT_NUMBER = "number";
/** @inheritdoc */
public function GetFormType(): string
{
return NumberType::class;
}
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_NUMBER, NumberIOFormat::class);
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
/**
* A block to manage a textarea.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class TextAreaFormBlock extends AbstractTypeFormBlock
{
/** @inheritdoc */
public function GetFormType(): string
{
return TextareaType::class;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Symfony\Component\Form\Extension\Core\Type\TextType;
/**
* A block to manage a text input.
* This block exposes a single text output.
*
* @package Combodo\iTop\Forms\Block\Base
* @since 3.3.0
*/
class TextFormBlock extends AbstractTypeFormBlock
{
// Outputs
public const OUTPUT_TEXT = "text";
/** @inheritdoc */
public function GetFormType(): string
{
return TextType::class;
}
/** @inheritdoc */
protected function RegisterIO(IORegister $oIORegister): void
{
parent::RegisterIO($oIORegister);
$oIORegister->AddOutput(self::OUTPUT_TEXT, StringIOFormat::class);
}
}