N°8772 - dynamic form

This commit is contained in:
Benjamin Dalsass
2025-10-30 10:38:23 +01:00
parent 4c9373d034
commit 68d2038488
40 changed files with 854 additions and 328 deletions

View File

@@ -36,18 +36,18 @@
margin: 20px 0;
}
#form select{
.form select{
padding: 0;
overflow-y: auto;
}
#form select option{
.form select option{
height: 30px;
display: flex;
align-items: center;
}
form[aria-busy="true"] {
.form[aria-busy="true"] {
opacity: .5;
}

View File

@@ -474,8 +474,11 @@ return array(
'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => $baseDir . '/sources/Form/Validator/NotEmptyExtKeyValidator.php',
'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => $baseDir . '/sources/Form/Validator/SelectObjectValidator.php',
'Combodo\\iTop\\Forms\\Block\\AbstractFormBlock' => $baseDir . '/sources/Forms/Block/AbstractFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\CheckboxFormBlock' => $baseDir . '/sources/Forms/Block/Base/CheckboxFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => $baseDir . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\CollectionBlock' => $baseDir . '/sources/Forms/Block/Base/CollectionBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\DateFormBlock' => $baseDir . '/sources/Forms/Block/Base/DateFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\DateTimeFormBlock' => $baseDir . '/sources/Forms/Block/Base/DateTimeFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => $baseDir . '/sources/Forms/Block/Base/FormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextAreaFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextFormBlock.php',
@@ -484,19 +487,20 @@ return array(
'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/OqlFormBlock.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\\AttributeFormType' => $baseDir . '/sources/Forms/Block/FormType/AttributeFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\AttributeValueFormType' => $baseDir . '/sources/Forms/Block/FormType/AttributeValueFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\ChoiceFormType' => $baseDir . '/sources/Forms/Block/FormType/ChoiceFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\CollectionFormType' => $baseDir . '/sources/Forms/Block/FormType/CollectionFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\FormType' => $baseDir . '/sources/Forms/Block/FormType/FormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\OqlFormType' => $baseDir . '/sources/Forms/Block/FormType/OqlFormType.php',
'Combodo\\iTop\\Forms\\Block\\IO\\AbstractFormIO' => $baseDir . '/sources/Forms/Block/IO/AbstractFormIO.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\AbstractConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/AbstractConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\OqlToClassConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/OqlToClassConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\StringToAttributeConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/StringToAttributeConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\StringToBooleanConverter' => $baseDir . '/sources/Forms/Block/IO/Converter/StringToBooleanConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\FormBinding' => $baseDir . '/sources/Forms/Block/IO/FormBinding.php',
'Combodo\\iTop\\Forms\\Block\\IO\\FormInput' => $baseDir . '/sources/Forms/Block/IO/FormInput.php',
'Combodo\\iTop\\Forms\\Block\\IO\\FormOutput' => $baseDir . '/sources/Forms/Block/IO/FormOutput.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\AttributeIOFormat' => $baseDir . '/sources/Forms/Block/IO/Format/AttributeIOFormat.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\BooleanIOFormat' => $baseDir . '/sources/Forms/Block/IO/Format/BooleanIOFormat.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\ClassIOFormat' => $baseDir . '/sources/Forms/Block/IO/Format/ClassIOFormat.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\RawFormat' => $baseDir . '/sources/Forms/Block/IO/Format/RawFormat.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => $baseDir . '/sources/Forms/FormBuilder/DependencyHandler.php',
@@ -509,6 +513,7 @@ return array(
'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => $baseDir . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php',
'Combodo\\iTop\\Forms\\Forms' => $baseDir . '/sources/Forms/Forms.php',
'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\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => $baseDir . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php',
@@ -1517,6 +1522,8 @@ return array(
'Symfony\\Bridge\\Twig\\Node\\StopwatchNode' => $vendorDir . '/symfony/twig-bridge/Node/StopwatchNode.php',
'Symfony\\Bridge\\Twig\\Node\\TransDefaultDomainNode' => $vendorDir . '/symfony/twig-bridge/Node/TransDefaultDomainNode.php',
'Symfony\\Bridge\\Twig\\Node\\TransNode' => $vendorDir . '/symfony/twig-bridge/Node/TransNode.php',
'Symfony\\Bridge\\Twig\\Test\\FormLayoutTestCase' => $vendorDir . '/symfony/twig-bridge/Test/FormLayoutTestCase.php',
'Symfony\\Bridge\\Twig\\Test\\Traits\\RuntimeLoaderProvider' => $vendorDir . '/symfony/twig-bridge/Test/Traits/RuntimeLoaderProvider.php',
'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php',
'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php',
'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php',
@@ -2450,6 +2457,13 @@ return array(
'Symfony\\Component\\Form\\SubmitButton' => $vendorDir . '/symfony/form/SubmitButton.php',
'Symfony\\Component\\Form\\SubmitButtonBuilder' => $vendorDir . '/symfony/form/SubmitButtonBuilder.php',
'Symfony\\Component\\Form\\SubmitButtonTypeInterface' => $vendorDir . '/symfony/form/SubmitButtonTypeInterface.php',
'Symfony\\Component\\Form\\Test\\FormBuilderInterface' => $vendorDir . '/symfony/form/Test/FormBuilderInterface.php',
'Symfony\\Component\\Form\\Test\\FormIntegrationTestCase' => $vendorDir . '/symfony/form/Test/FormIntegrationTestCase.php',
'Symfony\\Component\\Form\\Test\\FormInterface' => $vendorDir . '/symfony/form/Test/FormInterface.php',
'Symfony\\Component\\Form\\Test\\FormPerformanceTestCase' => $vendorDir . '/symfony/form/Test/FormPerformanceTestCase.php',
'Symfony\\Component\\Form\\Test\\Traits\\RunTestTrait' => $vendorDir . '/symfony/form/Test/Traits/RunTestTrait.php',
'Symfony\\Component\\Form\\Test\\Traits\\ValidatorExtensionTrait' => $vendorDir . '/symfony/form/Test/Traits/ValidatorExtensionTrait.php',
'Symfony\\Component\\Form\\Test\\TypeTestCase' => $vendorDir . '/symfony/form/Test/TypeTestCase.php',
'Symfony\\Component\\Form\\Util\\FormUtil' => $vendorDir . '/symfony/form/Util/FormUtil.php',
'Symfony\\Component\\Form\\Util\\InheritDataAwareIterator' => $vendorDir . '/symfony/form/Util/InheritDataAwareIterator.php',
'Symfony\\Component\\Form\\Util\\OptionsResolverWrapper' => $vendorDir . '/symfony/form/Util/OptionsResolverWrapper.php',
@@ -2551,6 +2565,17 @@ return array(
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php',
'Symfony\\Component\\HttpFoundation\\StreamedJsonResponse' => $vendorDir . '/symfony/http-foundation/StreamedJsonResponse.php',
'Symfony\\Component\\HttpFoundation\\StreamedResponse' => $vendorDir . '/symfony/http-foundation/StreamedResponse.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\RequestAttributeValueSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseCookieValueSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseFormatSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasCookie' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasHeader' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderLocationSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsRedirected' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsSuccessful' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsUnprocessable' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseStatusCodeSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php',
'Symfony\\Component\\HttpFoundation\\UriSigner' => $vendorDir . '/symfony/http-foundation/UriSigner.php',
'Symfony\\Component\\HttpFoundation\\UrlHelper' => $vendorDir . '/symfony/http-foundation/UrlHelper.php',
'Symfony\\Component\\HttpKernel\\Attribute\\AsController' => $vendorDir . '/symfony/http-kernel/Attribute/AsController.php',
@@ -2747,6 +2772,9 @@ return array(
'Symfony\\Component\\Mailer\\Messenger\\MessageHandler' => $vendorDir . '/symfony/mailer/Messenger/MessageHandler.php',
'Symfony\\Component\\Mailer\\Messenger\\SendEmailMessage' => $vendorDir . '/symfony/mailer/Messenger/SendEmailMessage.php',
'Symfony\\Component\\Mailer\\SentMessage' => $vendorDir . '/symfony/mailer/SentMessage.php',
'Symfony\\Component\\Mailer\\Test\\Constraint\\EmailCount' => $vendorDir . '/symfony/mailer/Test/Constraint/EmailCount.php',
'Symfony\\Component\\Mailer\\Test\\Constraint\\EmailIsQueued' => $vendorDir . '/symfony/mailer/Test/Constraint/EmailIsQueued.php',
'Symfony\\Component\\Mailer\\Test\\TransportFactoryTestCase' => $vendorDir . '/symfony/mailer/Test/TransportFactoryTestCase.php',
'Symfony\\Component\\Mailer\\Transport' => $vendorDir . '/symfony/mailer/Transport.php',
'Symfony\\Component\\Mailer\\Transport\\AbstractApiTransport' => $vendorDir . '/symfony/mailer/Transport/AbstractApiTransport.php',
'Symfony\\Component\\Mailer\\Transport\\AbstractHttpTransport' => $vendorDir . '/symfony/mailer/Transport/AbstractHttpTransport.php',
@@ -2837,6 +2865,13 @@ return array(
'Symfony\\Component\\Mime\\Part\\SMimePart' => $vendorDir . '/symfony/mime/Part/SMimePart.php',
'Symfony\\Component\\Mime\\Part\\TextPart' => $vendorDir . '/symfony/mime/Part/TextPart.php',
'Symfony\\Component\\Mime\\RawMessage' => $vendorDir . '/symfony/mime/RawMessage.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailAddressContains' => $vendorDir . '/symfony/mime/Test/Constraint/EmailAddressContains.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailAttachmentCount' => $vendorDir . '/symfony/mime/Test/Constraint/EmailAttachmentCount.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailHasHeader' => $vendorDir . '/symfony/mime/Test/Constraint/EmailHasHeader.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailHeaderSame' => $vendorDir . '/symfony/mime/Test/Constraint/EmailHeaderSame.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailHtmlBodyContains' => $vendorDir . '/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailSubjectContains' => $vendorDir . '/symfony/mime/Test/Constraint/EmailSubjectContains.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailTextBodyContains' => $vendorDir . '/symfony/mime/Test/Constraint/EmailTextBodyContains.php',
'Symfony\\Component\\OptionsResolver\\Debug\\OptionsResolverIntrospector' => $vendorDir . '/symfony/options-resolver/Debug/OptionsResolverIntrospector.php',
'Symfony\\Component\\OptionsResolver\\Exception\\AccessException' => $vendorDir . '/symfony/options-resolver/Exception/AccessException.php',
'Symfony\\Component\\OptionsResolver\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/options-resolver/Exception/ExceptionInterface.php',
@@ -3062,6 +3097,7 @@ return array(
'Symfony\\Component\\Security\\Core\\Signature\\Exception\\InvalidSignatureException' => $vendorDir . '/symfony/security-core/Signature/Exception/InvalidSignatureException.php',
'Symfony\\Component\\Security\\Core\\Signature\\ExpiredSignatureStorage' => $vendorDir . '/symfony/security-core/Signature/ExpiredSignatureStorage.php',
'Symfony\\Component\\Security\\Core\\Signature\\SignatureHasher' => $vendorDir . '/symfony/security-core/Signature/SignatureHasher.php',
'Symfony\\Component\\Security\\Core\\Test\\AccessDecisionStrategyTestCase' => $vendorDir . '/symfony/security-core/Test/AccessDecisionStrategyTestCase.php',
'Symfony\\Component\\Security\\Core\\User\\AttributesBasedUserProviderInterface' => $vendorDir . '/symfony/security-core/User/AttributesBasedUserProviderInterface.php',
'Symfony\\Component\\Security\\Core\\User\\ChainUserChecker' => $vendorDir . '/symfony/security-core/User/ChainUserChecker.php',
'Symfony\\Component\\Security\\Core\\User\\ChainUserProvider' => $vendorDir . '/symfony/security-core/User/ChainUserProvider.php',
@@ -3171,6 +3207,7 @@ return array(
'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => $vendorDir . '/symfony/var-dumper/Exception/ThrowingCasterException.php',
'Symfony\\Component\\VarDumper\\Server\\Connection' => $vendorDir . '/symfony/var-dumper/Server/Connection.php',
'Symfony\\Component\\VarDumper\\Server\\DumpServer' => $vendorDir . '/symfony/var-dumper/Server/DumpServer.php',
'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => $vendorDir . '/symfony/var-dumper/Test/VarDumperTestTrait.php',
'Symfony\\Component\\VarDumper\\VarDumper' => $vendorDir . '/symfony/var-dumper/VarDumper.php',
'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/var-exporter/Exception/ClassNotFoundException.php',
'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/var-exporter/Exception/ExceptionInterface.php',
@@ -3518,6 +3555,8 @@ return array(
'Twig\\Source' => $vendorDir . '/twig/twig/src/Source.php',
'Twig\\Template' => $vendorDir . '/twig/twig/src/Template.php',
'Twig\\TemplateWrapper' => $vendorDir . '/twig/twig/src/TemplateWrapper.php',
'Twig\\Test\\IntegrationTestCase' => $vendorDir . '/twig/twig/src/Test/IntegrationTestCase.php',
'Twig\\Test\\NodeTestCase' => $vendorDir . '/twig/twig/src/Test/NodeTestCase.php',
'Twig\\Token' => $vendorDir . '/twig/twig/src/Token.php',
'Twig\\TokenParser\\AbstractTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AbstractTokenParser.php',
'Twig\\TokenParser\\ApplyTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ApplyTokenParser.php',

View File

@@ -855,8 +855,11 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/NotEmptyExtKeyValidator.php',
'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/SelectObjectValidator.php',
'Combodo\\iTop\\Forms\\Block\\AbstractFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/AbstractFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\CheckboxFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/CheckboxFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\CollectionBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/CollectionBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\DateFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/DateFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\DateTimeFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/DateTimeFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/FormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextAreaFormBlock.php',
'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextFormBlock.php',
@@ -865,19 +868,20 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/OqlFormBlock.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\\AttributeFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/AttributeFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\AttributeValueFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/AttributeValueFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\ChoiceFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/ChoiceFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\CollectionFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/CollectionFormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\FormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/FormType.php',
'Combodo\\iTop\\Forms\\Block\\FormType\\OqlFormType' => __DIR__ . '/../..' . '/sources/Forms/Block/FormType/OqlFormType.php',
'Combodo\\iTop\\Forms\\Block\\IO\\AbstractFormIO' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/AbstractFormIO.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\AbstractConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/AbstractConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\OqlToClassConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/OqlToClassConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\StringToAttributeConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/StringToAttributeConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Converter\\StringToBooleanConverter' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Converter/StringToBooleanConverter.php',
'Combodo\\iTop\\Forms\\Block\\IO\\FormBinding' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormBinding.php',
'Combodo\\iTop\\Forms\\Block\\IO\\FormInput' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormInput.php',
'Combodo\\iTop\\Forms\\Block\\IO\\FormOutput' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/FormOutput.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\AttributeIOFormat' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Format/AttributeIOFormat.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\BooleanIOFormat' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Format/BooleanIOFormat.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\ClassIOFormat' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Format/ClassIOFormat.php',
'Combodo\\iTop\\Forms\\Block\\IO\\Format\\RawFormat' => __DIR__ . '/../..' . '/sources/Forms/Block/IO/Format/RawFormat.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyHandler.php',
@@ -890,6 +894,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php',
'Combodo\\iTop\\Forms\\Forms' => __DIR__ . '/../..' . '/sources/Forms/Forms.php',
'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\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => __DIR__ . '/../..' . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php',
@@ -1898,6 +1903,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Bridge\\Twig\\Node\\StopwatchNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/StopwatchNode.php',
'Symfony\\Bridge\\Twig\\Node\\TransDefaultDomainNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/TransDefaultDomainNode.php',
'Symfony\\Bridge\\Twig\\Node\\TransNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/TransNode.php',
'Symfony\\Bridge\\Twig\\Test\\FormLayoutTestCase' => __DIR__ . '/..' . '/symfony/twig-bridge/Test/FormLayoutTestCase.php',
'Symfony\\Bridge\\Twig\\Test\\Traits\\RuntimeLoaderProvider' => __DIR__ . '/..' . '/symfony/twig-bridge/Test/Traits/RuntimeLoaderProvider.php',
'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php',
'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php',
'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php',
@@ -2831,6 +2838,13 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Form\\SubmitButton' => __DIR__ . '/..' . '/symfony/form/SubmitButton.php',
'Symfony\\Component\\Form\\SubmitButtonBuilder' => __DIR__ . '/..' . '/symfony/form/SubmitButtonBuilder.php',
'Symfony\\Component\\Form\\SubmitButtonTypeInterface' => __DIR__ . '/..' . '/symfony/form/SubmitButtonTypeInterface.php',
'Symfony\\Component\\Form\\Test\\FormBuilderInterface' => __DIR__ . '/..' . '/symfony/form/Test/FormBuilderInterface.php',
'Symfony\\Component\\Form\\Test\\FormIntegrationTestCase' => __DIR__ . '/..' . '/symfony/form/Test/FormIntegrationTestCase.php',
'Symfony\\Component\\Form\\Test\\FormInterface' => __DIR__ . '/..' . '/symfony/form/Test/FormInterface.php',
'Symfony\\Component\\Form\\Test\\FormPerformanceTestCase' => __DIR__ . '/..' . '/symfony/form/Test/FormPerformanceTestCase.php',
'Symfony\\Component\\Form\\Test\\Traits\\RunTestTrait' => __DIR__ . '/..' . '/symfony/form/Test/Traits/RunTestTrait.php',
'Symfony\\Component\\Form\\Test\\Traits\\ValidatorExtensionTrait' => __DIR__ . '/..' . '/symfony/form/Test/Traits/ValidatorExtensionTrait.php',
'Symfony\\Component\\Form\\Test\\TypeTestCase' => __DIR__ . '/..' . '/symfony/form/Test/TypeTestCase.php',
'Symfony\\Component\\Form\\Util\\FormUtil' => __DIR__ . '/..' . '/symfony/form/Util/FormUtil.php',
'Symfony\\Component\\Form\\Util\\InheritDataAwareIterator' => __DIR__ . '/..' . '/symfony/form/Util/InheritDataAwareIterator.php',
'Symfony\\Component\\Form\\Util\\OptionsResolverWrapper' => __DIR__ . '/..' . '/symfony/form/Util/OptionsResolverWrapper.php',
@@ -2932,6 +2946,17 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php',
'Symfony\\Component\\HttpFoundation\\StreamedJsonResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedJsonResponse.php',
'Symfony\\Component\\HttpFoundation\\StreamedResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedResponse.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\RequestAttributeValueSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseCookieValueSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseFormatSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasCookie' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasHeader' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderLocationSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsRedirected' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsSuccessful' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsUnprocessable' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php',
'Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseStatusCodeSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php',
'Symfony\\Component\\HttpFoundation\\UriSigner' => __DIR__ . '/..' . '/symfony/http-foundation/UriSigner.php',
'Symfony\\Component\\HttpFoundation\\UrlHelper' => __DIR__ . '/..' . '/symfony/http-foundation/UrlHelper.php',
'Symfony\\Component\\HttpKernel\\Attribute\\AsController' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/AsController.php',
@@ -3128,6 +3153,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Mailer\\Messenger\\MessageHandler' => __DIR__ . '/..' . '/symfony/mailer/Messenger/MessageHandler.php',
'Symfony\\Component\\Mailer\\Messenger\\SendEmailMessage' => __DIR__ . '/..' . '/symfony/mailer/Messenger/SendEmailMessage.php',
'Symfony\\Component\\Mailer\\SentMessage' => __DIR__ . '/..' . '/symfony/mailer/SentMessage.php',
'Symfony\\Component\\Mailer\\Test\\Constraint\\EmailCount' => __DIR__ . '/..' . '/symfony/mailer/Test/Constraint/EmailCount.php',
'Symfony\\Component\\Mailer\\Test\\Constraint\\EmailIsQueued' => __DIR__ . '/..' . '/symfony/mailer/Test/Constraint/EmailIsQueued.php',
'Symfony\\Component\\Mailer\\Test\\TransportFactoryTestCase' => __DIR__ . '/..' . '/symfony/mailer/Test/TransportFactoryTestCase.php',
'Symfony\\Component\\Mailer\\Transport' => __DIR__ . '/..' . '/symfony/mailer/Transport.php',
'Symfony\\Component\\Mailer\\Transport\\AbstractApiTransport' => __DIR__ . '/..' . '/symfony/mailer/Transport/AbstractApiTransport.php',
'Symfony\\Component\\Mailer\\Transport\\AbstractHttpTransport' => __DIR__ . '/..' . '/symfony/mailer/Transport/AbstractHttpTransport.php',
@@ -3218,6 +3246,13 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Mime\\Part\\SMimePart' => __DIR__ . '/..' . '/symfony/mime/Part/SMimePart.php',
'Symfony\\Component\\Mime\\Part\\TextPart' => __DIR__ . '/..' . '/symfony/mime/Part/TextPart.php',
'Symfony\\Component\\Mime\\RawMessage' => __DIR__ . '/..' . '/symfony/mime/RawMessage.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailAddressContains' => __DIR__ . '/..' . '/symfony/mime/Test/Constraint/EmailAddressContains.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailAttachmentCount' => __DIR__ . '/..' . '/symfony/mime/Test/Constraint/EmailAttachmentCount.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailHasHeader' => __DIR__ . '/..' . '/symfony/mime/Test/Constraint/EmailHasHeader.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailHeaderSame' => __DIR__ . '/..' . '/symfony/mime/Test/Constraint/EmailHeaderSame.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailHtmlBodyContains' => __DIR__ . '/..' . '/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailSubjectContains' => __DIR__ . '/..' . '/symfony/mime/Test/Constraint/EmailSubjectContains.php',
'Symfony\\Component\\Mime\\Test\\Constraint\\EmailTextBodyContains' => __DIR__ . '/..' . '/symfony/mime/Test/Constraint/EmailTextBodyContains.php',
'Symfony\\Component\\OptionsResolver\\Debug\\OptionsResolverIntrospector' => __DIR__ . '/..' . '/symfony/options-resolver/Debug/OptionsResolverIntrospector.php',
'Symfony\\Component\\OptionsResolver\\Exception\\AccessException' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/AccessException.php',
'Symfony\\Component\\OptionsResolver\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/options-resolver/Exception/ExceptionInterface.php',
@@ -3443,6 +3478,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Security\\Core\\Signature\\Exception\\InvalidSignatureException' => __DIR__ . '/..' . '/symfony/security-core/Signature/Exception/InvalidSignatureException.php',
'Symfony\\Component\\Security\\Core\\Signature\\ExpiredSignatureStorage' => __DIR__ . '/..' . '/symfony/security-core/Signature/ExpiredSignatureStorage.php',
'Symfony\\Component\\Security\\Core\\Signature\\SignatureHasher' => __DIR__ . '/..' . '/symfony/security-core/Signature/SignatureHasher.php',
'Symfony\\Component\\Security\\Core\\Test\\AccessDecisionStrategyTestCase' => __DIR__ . '/..' . '/symfony/security-core/Test/AccessDecisionStrategyTestCase.php',
'Symfony\\Component\\Security\\Core\\User\\AttributesBasedUserProviderInterface' => __DIR__ . '/..' . '/symfony/security-core/User/AttributesBasedUserProviderInterface.php',
'Symfony\\Component\\Security\\Core\\User\\ChainUserChecker' => __DIR__ . '/..' . '/symfony/security-core/User/ChainUserChecker.php',
'Symfony\\Component\\Security\\Core\\User\\ChainUserProvider' => __DIR__ . '/..' . '/symfony/security-core/User/ChainUserProvider.php',
@@ -3552,6 +3588,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => __DIR__ . '/..' . '/symfony/var-dumper/Exception/ThrowingCasterException.php',
'Symfony\\Component\\VarDumper\\Server\\Connection' => __DIR__ . '/..' . '/symfony/var-dumper/Server/Connection.php',
'Symfony\\Component\\VarDumper\\Server\\DumpServer' => __DIR__ . '/..' . '/symfony/var-dumper/Server/DumpServer.php',
'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => __DIR__ . '/..' . '/symfony/var-dumper/Test/VarDumperTestTrait.php',
'Symfony\\Component\\VarDumper\\VarDumper' => __DIR__ . '/..' . '/symfony/var-dumper/VarDumper.php',
'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ClassNotFoundException.php',
'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ExceptionInterface.php',
@@ -3899,6 +3936,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Twig\\Source' => __DIR__ . '/..' . '/twig/twig/src/Source.php',
'Twig\\Template' => __DIR__ . '/..' . '/twig/twig/src/Template.php',
'Twig\\TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/src/TemplateWrapper.php',
'Twig\\Test\\IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/IntegrationTestCase.php',
'Twig\\Test\\NodeTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/NodeTestCase.php',
'Twig\\Token' => __DIR__ . '/..' . '/twig/twig/src/Token.php',
'Twig\\TokenParser\\AbstractTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AbstractTokenParser.php',
'Twig\\TokenParser\\ApplyTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ApplyTokenParser.php',

View File

@@ -6,8 +6,8 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
$vendorDir . '/pear/archive_tar',
$vendorDir . '/pear/pear_exception',
$vendorDir . '/pear/console_getopt',
$vendorDir . '/pear/pear-core-minimal/src',
$vendorDir . '/pear/pear_exception',
$vendorDir . '/pear/archive_tar',
);

View File

@@ -3,7 +3,7 @@
'name' => 'combodo/itop',
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => '600a6185a35e5e92bddd4e05894ae39f83a442d6',
'reference' => '2c6c084a0f4dc7b999db805c75aa3314fa297d33',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -22,7 +22,7 @@
'combodo/itop' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => '600a6185a35e5e92bddd4e05894ae39f83a442d6',
'reference' => '2c6c084a0f4dc7b999db805c75aa3314fa297d33',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View File

@@ -0,0 +1,7 @@
parameters:
level: 6
errorFormat: raw
editorUrl: '%%file%% %%line%% %%column%%: %%error%%'
paths:
- src
- tests

19
lib/symfony/mime/LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2010-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -8,8 +8,11 @@ namespace Combodo\iTop\Forms\Block;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\IO\Converter\AbstractConverter;
use Combodo\iTop\Forms\Block\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\Block\IO\Format\RawFormat;
use Combodo\iTop\Forms\Block\IO\FormInput;
use Combodo\iTop\Forms\Block\IO\FormOutput;
use Combodo\iTop\Forms\IFormBlock;
use IssueLog;
use Symfony\Component\Filesystem\Exception\IOException;
@@ -21,11 +24,23 @@ use Symfony\Component\Filesystem\Exception\IOException;
* It defines its inputs and outputs.
*
*/
abstract class AbstractFormBlock
abstract class AbstractFormBlock implements IFormBlock
{
// Inputs
public const INPUT_VISIBLE = 'visible';
// Outputs
public const OUTPUT_VALUE = 'value';
/** @var null|FormBlock */
private ?FormBlock $oParent = null;
/** @var array form options */
private array $aOptions = [];
/** @var array form dynamic options */
protected array $aDynamicOptions = [];
/** @var array form block inputs */
private array $aFormInputs = [];
@@ -35,6 +50,7 @@ abstract class AbstractFormBlock
/** @var bool flag indicating the form insertion */
private bool $bIsAddedToForm = false;
/**
* Return the form type.
*
@@ -42,26 +58,18 @@ abstract class AbstractFormBlock
*/
abstract public function GetFormType(): string;
/**
* Initialize options.
*
* @return array
*/
abstract public function InitOptions(): array;
/**
* Constructor.
*
* @param string $sName
* @param array $aOptions
* @param array $aUserOptions
*/
public function __construct(private readonly string $sName, protected array $aOptions = [])
public function __construct(private readonly string $sName, protected array $aUserOptions = [])
{
// Attach the form block
$this->aOptions['form_block'] = $this;
// Compute options
$this->aOptions = array_merge($this->aOptions, $this->InitOptions());
$this->aOptions = $aUserOptions;
$this->InitBlockOptions($this->aOptions);
// Initialize block inputs
$this->InitInputs();
@@ -70,6 +78,17 @@ abstract class AbstractFormBlock
$this->InitOutputs();
}
/**
* Initialize options.
*
* @param array $aUserOptions
*
*/
public function InitBlockOptions(array &$aUserOptions): void
{
$aUserOptions['form_block'] = $this;
}
/**
* Set the parent block.
*
@@ -113,15 +132,24 @@ abstract class AbstractFormBlock
return $this->aOptions;
}
/**
* Return the form block options.
* Options will be passed to FormType for building.
*
* @return array
*/
public function UpdateOptions(): array
public function GetOptionsMergedWithDynamic(string $sEventType = null): array
{
return $this->aOptions;
return array_merge($this->aDynamicOptions, $this->aOptions);
}
public function GetDynamicOptions(string $sEventType = null): array
{
return $this->aDynamicOptions;
}
/**
* @param string|null $sEventType
*
* @return void
*/
public function UpdateDynamicOptions(string $sEventType = null): void
{
$this->aDynamicOptions = [];
}
/**
@@ -236,7 +264,6 @@ abstract class AbstractFormBlock
*
* @return $this
* @throws FormBlockException
* @throws FormBlockIOException
*/
public function DependsOnParent(string $sInputName, string $sParentInputName): AbstractFormBlock
{
@@ -339,13 +366,15 @@ abstract class AbstractFormBlock
/**
* Inputs data ready.
*
* @param string|null $sType
*
* @return bool
*/
public function IsInputsDataReady(): bool
public function IsInputsDataReady(string $sType = null): bool
{
foreach ($this->aFormInputs as $oFormInput) {
if ($oFormInput->IsBound()) {
if (!$oFormInput->IsDataReady()) {
if (!$oFormInput->IsEventDataReady($sType)) {
return false;
}
}
@@ -419,7 +448,20 @@ abstract class AbstractFormBlock
*/
public function InitInputs(): void
{
$this->AddInput(self::INPUT_VISIBLE, BooleanIOFormat::class);
}
public function IsVisible(string $sEventType = null): bool
{
$oInput = $this->GetInput(self::INPUT_VISIBLE);
if(!$oInput->IsBound()){
return true;
}
$bVisible = $oInput->GetValue($sEventType);
return $bVisible !== null && $bVisible->IsTrue();
}
/**
@@ -427,16 +469,16 @@ abstract class AbstractFormBlock
*
* @return void
*/
public function InitOutputs()
public function InitOutputs(): void
{
$this->AddOutput(self::OUTPUT_VALUE, RawFormat::class);
}
/**
* @return true
*/
public function AllowAdd(): bool
public function AllowAdd(string $sEventType = null): bool
{
return true;
}

View File

@@ -0,0 +1,41 @@
<?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\IO\Converter\StringToBooleanConverter;
use Combodo\iTop\Forms\Block\IO\Format\BooleanIOFormat;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
/**
* Form block for checkbox.
*
*/
class CheckboxFormBlock extends AbstractFormBlock
{
// outputs
public const OUTPUT_CHECKED = 'checked';
/** @inheritdoc */
public function GetFormType(): string
{
return CheckboxType::class;
}
function InitBlockOptions(array &$aUserOptions): void
{
parent::InitBlockOptions($aUserOptions);
$aUserOptions['required'] = false;
}
/** @inheritdoc */
function InitOutputs(): void
{
parent::InitOutputs();
$this->AddOutput(self::OUTPUT_CHECKED, BooleanIOFormat::class, new StringToBooleanConverter());
}
}

View File

@@ -21,10 +21,4 @@ class ChoiceFormBlock extends AbstractFormBlock
return ChoiceFormType::class;
}
/** @inheritdoc */
public function InitOptions(): array
{
return [
];
}
}

View File

@@ -7,6 +7,8 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\FormType\CollectionFormType;
use Combodo\iTop\Forms\Block\IO\Format\ClassIOFormat;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
/**
@@ -15,33 +17,54 @@ use Symfony\Component\Form\Extension\Core\Type\CollectionType;
*/
class CollectionBlock extends AbstractFormBlock
{
// Inputs
public const INPUT_CLASS_NAME = 'class_name';
/**
* Constructor.
*
* @param string $sName
* @param array $aOptions
*/
public function __construct(string $sName, array $aOptions = [])
{
parent::__construct($sName, $aOptions);
}
/** @inheritdoc */
public function GetFormType(): string
{
return CollectionType::class;
return CollectionFormType::class;
}
/** @inheritdoc */
public function InitOptions(): array
public function InitInputs(): void
{
$sBlockEntryType = $this->GetOptions()['block_entry_type'];
$sBlockEntryOptions = $this->GetOptions()['block_entry_options'];
parent::InitInputs();
$this->AddInput(self::INPUT_CLASS_NAME, ClassIOFormat::class);
}
$this->aOptions = [];
/** @inheritdoc */
public function InitBlockOptions(array &$aUserOptions): void
{
parent::InitBlockOptions($aUserOptions);
$oBlock = new ($sBlockEntryType)('prototype', $sBlockEntryOptions);
return [
'entry_type' => $oBlock->GetFormType(),
];
// Convert block information in type information
if(isset($aUserOptions['block_entry_type'])) {
$sBlockEntryType = $aUserOptions['block_entry_type'];
$sBlockEntryOptions = $this->aUserOptions['block_entry_options'];
$oBlock = new ($sBlockEntryType)('prototype', $sBlockEntryOptions);
unset($aUserOptions['block_entry_type']);
unset($aUserOptions['block_entry_options']);
$aUserOptions['entry_type'] = $oBlock->GetFormType();
$aUserOptions['entry_options'] = $oBlock->GetOptions();
$aUserOptions['prototype'] = true;
$aUserOptions['allow_add'] = true;
$aUserOptions['prototype_options'] = [
'label' => false
];
}
}
}

View File

@@ -0,0 +1,32 @@
<?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 Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
/**
* Form block for date.
*
*/
class DateFormBlock extends AbstractFormBlock
{
/** @inheritdoc */
public function GetFormType(): string
{
return DateType::class;
}
public function InitBlockOptions(array &$aUserOptions): void
{
parent::InitBlockOptions($aUserOptions);
$aUserOptions['widget'] = 'single_text';
}
}

View File

@@ -0,0 +1,31 @@
<?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 Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
/**
* Form block for date time.
*
*/
class DateTimeFormBlock extends AbstractFormBlock
{
/** @inheritdoc */
public function GetFormType(): string
{
return DateTimeType::class;
}
public function InitBlockOptions(array &$aUserOptions): void
{
parent::InitBlockOptions($aUserOptions);
$aUserOptions['widget'] = 'single_text';
}
}

View File

@@ -7,9 +7,11 @@
namespace Combodo\iTop\Forms\Block\Base;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Block\FormType\FormType;
use Combodo\iTop\Forms\FormsException;
use Exception;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use ReflectionClass;
/**
* Complex form type.
@@ -48,24 +50,35 @@ class FormBlock extends AbstractFormBlock
}
/** @inheritdoc */
public function InitOptions(): array
public function InitBlockOptions(array &$aUserOptions): void
{
return [
'compound' => true,
parent::InitBlockOptions($aUserOptions);
$aUserOptions['compound'] = true;
$aUserOptions['attr'] = [
'class' => 'form'
];
}
/**
* Add a child form.
*
* @param string $sType block class name
* @param string $sName block name
* @param string $sType block class name
* @param array $aOptions options
*
* @return $this
* @throws \ReflectionException
*/
public function Add(string $sType, string $sName, array $aOptions): AbstractFormBlock
public function Add(string $sName, string $sType, array $aOptions): AbstractFormBlock
{
$oRef = new ReflectionClass($sType);
if($oRef->isSubclassOf(AbstractFormBlock::class) === false){
throw new FormBlockException("The block type '$sType' is not a subclass of AbstractFormBlock.");
}
$aOptions['priority'] = -count($this->aChildrenBlocks);
$oSubFormBlock = new ($sType)($sName, $aOptions);
$this->aChildrenBlocks[$sName] = $oSubFormBlock;
$oSubFormBlock->SetParent($this);

View File

@@ -21,9 +21,4 @@ class TextAreaFormBlock extends AbstractFormBlock
return TextareaType::class;
}
/** @inheritdoc */
public function InitOptions(): array
{
return [];
}
}

View File

@@ -21,9 +21,5 @@ class TextFormBlock extends AbstractFormBlock
return TextType::class;
}
/** @inheritdoc */
public function InitOptions(): array
{
return [];
}
}

View File

@@ -28,12 +28,11 @@ class AttributeChoiceFormBlock extends ChoiceFormBlock
public const OUTPUT_ATTRIBUTE = 'attribute';
/** @inheritdoc */
public function InitOptions(): array
public function InitBlockOptions(array &$aUserOptions): void
{
$aOptions = parent::InitOptions();
parent::InitBlockOptions($aUserOptions);
// $aOptions['placeholder'] = 'Select an attribute...';
return $aOptions;
$aUserOptions['placeholder'] = 'Select an attribute...';
}
/** @inheritdoc */
@@ -50,32 +49,13 @@ class AttributeChoiceFormBlock extends ChoiceFormBlock
$this->AddOutput(self::OUTPUT_ATTRIBUTE, AttributeIOFormat::class, new StringToAttributeConverter());
}
/** @inheritdoc
* @throws FormBlockException
*/
public function AllowAdd(): bool
/** @inheritdoc */
public function UpdateDynamicOptions(string $sEventType = null): void
{
return $this->GetInput(self::INPUT_CLASS_NAME)->Value() != '';
}
/** @inheritdoc
* @throws FormBlockException
* @throws CoreException
*/
public function UpdateOptions(): array
{
$aOptions = parent::GetOptions();
$oValue = $this->GetInput(self::INPUT_CLASS_NAME)->Value();
if ($oValue == '') {
return $aOptions;
}
$oValue = $this->GetInput(self::INPUT_CLASS_NAME)->GetValue($sEventType);
$aAttributeCodes = MetaModel::GetAttributesList($oValue);
$aAttributeCodes = array_combine($aAttributeCodes, $aAttributeCodes);
$aOptions['choices'] = $aAttributeCodes;
return $aOptions;
$this->aDynamicOptions['choices'] = array_combine($aAttributeCodes, $aAttributeCodes);
}
}

View File

@@ -29,15 +29,14 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
public const OUTPUT_VALUE = 'value';
/** @inheritdoc */
public function InitOptions(array &$aOptions = []): array
public function InitBlockOptions(array &$aUserOptions): void
{
$aOptions['multiple'] = true;
$aOptions['attr'] = [
parent::InitBlockOptions($aUserOptions);
$aUserOptions['multiple'] = true;
$aUserOptions['attr'] = [
'size' => 5,
'style' => 'height: auto;',
];
return $aOptions;
}
/** @inheritdoc */
@@ -55,51 +54,18 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
$this->AddOutput(self::OUTPUT_VALUE, RawFormat::class);
}
/** @inheritdoc
* @throws FormBlockException
* @throws Exception
*/
public function AllowAdd(): bool
/** @inheritdoc */
public function UpdateDynamicOptions(string $sEventType = null): void
{
$bDependentOk = $this->GetInput(self::INPUT_CLASS_NAME)->Value() != '' && $this->GetInput(self::INPUT_ATTRIBUTE)->Value() != '';
$oClassName = $this->GetInput(self::INPUT_CLASS_NAME)->GetValue($sEventType);
$oAttribute = $this->GetInput(self::INPUT_ATTRIBUTE)->GetValue($sEventType);
if ($bDependentOk) {
$oAttDef = MetaModel::GetAttributeDef($this->GetInput(self::INPUT_CLASS_NAME)->Value()->__toString(), $this->GetInput(self::INPUT_ATTRIBUTE)->Value()->__toString());
try{
$oAttDef = MetaModel::GetAttributeDef(strval($oClassName), strval($oAttribute));
$aValues = $oAttDef->GetAllowedValues();
return $aValues != null;
} else {
return false;
$this->aDynamicOptions['choices'] = array_flip($aValues ?? []);
}
}
/** @inheritdoc
* @throws Exception
*/
public function UpdateOptions(): array
{
$aOptions = parent::GetOptions();
$oClassName = $this->GetInput(self::INPUT_CLASS_NAME)->Value();
if ($oClassName == '') {
return $aOptions;
}
$oAttribute = $this->GetInput(self::INPUT_ATTRIBUTE)->Value();
if ($oAttribute == '') {
return $aOptions;
}
$oAttDef = MetaModel::GetAttributeDef(strval($oClassName), strval($oAttribute));
$aValues = $oAttDef->GetAllowedValues();
if ($aValues === null) {
return $aOptions;
}
$aOptions['choices'] = array_flip($aValues ?? []);
return $aOptions;
catch(Exception){}
}
}

View File

@@ -35,13 +35,9 @@ class OqlFormBlock extends TextAreaFormBlock
}
/** @inheritdoc */
public function InitOptions(): array
public function InitBlockOptions(array &$aUserOptions): void
{
$aOptions = parent::InitOptions();
$aOptions['with_ai_button'] = true;
return $aOptions;
parent::InitBlockOptions($aUserOptions);
$aUserOptions['with_ai_button'] = true;
}
}

View File

@@ -1,18 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\FormType;
use Symfony\Component\Form\AbstractType;
class AttributeFormType extends AbstractType
{
public function getParent(): string
{
return ChoiceFormType::class;
}
}

View File

@@ -1,33 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Forms\Block\FormType;
use MetaModel;
use Symfony\Component\Form\AbstractType;
class AttributeValueFormType extends AbstractType
{
public function getParent(): string
{
return ChoiceFormType::class;
}
public static function GetOptionsFromInputs(array $inputs): array
{
$aValues = [];
if (!empty($inputs['attribute'])) {
$oAttDef = MetaModel::GetAttributeDef($inputs['object_class'], $inputs['attribute']);
$aValues = $oAttDef->GetAllowedValues();
$aValues = $aValues !== null ? array_combine($aValues, $aValues) : [];
}
return [
'choices' => $aValues,
];
}
}

View File

@@ -45,6 +45,20 @@ class ChoiceFormType extends AbstractType
// on pre submit
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) use ($options) {
if($options['multiple'] === false && $options['required'] === true) {
if ($event->getData() === null) {
$FirstElement = array_shift($options['choices']);
if($FirstElement !== null){
$event->setData($FirstElement);
}
}
}
});
// on pre submit
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) use ($options){
// reset value if not in available choices
if (!empty($event->getData()) && !$this->CheckValue($event->getData(), $options)) {
$event->getForm()->addError(new FormError("The value has been reset because it is not part of the available choices anymore."));

View File

@@ -8,6 +8,8 @@ namespace Combodo\iTop\Forms\Block\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -21,16 +23,22 @@ class CollectionFormType extends AbstractType
return CollectionType::class;
}
public function configureOptions(OptionsResolver $resolver)
/** @inheritdoc */
public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
$resolver->setDefined([
'block_entry_type',
'block_entry_options',
$resolver->setDefaults([
'button_label' => 'Add an item',
]);
}
/** @inheritdoc */
public function buildView(FormView $view, FormInterface $form, array $options): void
{
parent::buildView($view, $form, $options);
$view->vars['button_label'] = $options['button_label'];
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Combodo\iTop\Forms\Block\FormType;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class FormType extends AbstractType
{
public function getParent(): string
{
return \Symfony\Component\Form\Extension\Core\Type\FormType::class;
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
parent::buildView($view, $form, $options);
/** @var FormBlock $oBlock */
$oBlock = $options['form_block'];
$aData = [];
foreach($oBlock->GetChildren() as $oChild) {
if($oChild->IsAdded()){
$oFormChild = $form->get($oChild->GetName());
$aData[] = [
'name' => $oChild->GetName(),
'added' => $oChild->IsAdded(),
'path' => $oFormChild->getPropertyPath(),
];
}
else{
$aData[] = [
'name' => $oChild->GetName(),
'added' => $oChild->IsAdded(),
];
}
}
$view->vars['blocks'] = $aData;
}
}

View File

@@ -6,25 +6,21 @@
namespace Combodo\iTop\Forms\Block\FormType;
use Combodo\iTop\Forms\Block\IO\Converter\OqlToClassConverter;
use Exception;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Event\PostSubmitEvent;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OqlFormType extends AbstractType
{
/** @inheritdoc */
public function getParent(): string
{
return TextareaType::class;
}
/** @inheritdoc */
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefault('help', 'An OQL query expression');
@@ -48,26 +44,7 @@ class OqlFormType extends AbstractType
$resolver->setDefined('with_ai_button');
}
/** @inheritdoc */
public function buildForm(FormBuilderInterface $builder, array $options): void
{
parent::buildForm($builder, $options);
// on pre submit
$builder->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) use ($options) {
try {
$oClassConverter = new OqlToClassConverter();
$oClassConverter->Convert($event->getData());
}
catch (Exception $e) {
$event->getForm()->addError(new FormError($e->getMessage()));
}
});
}
/** @inheritdoc */
/** @inheritdoc */
public function buildView(FormView $view, FormInterface $form, array $options): void
{
parent::buildView($view, $form, $options);

View File

@@ -107,12 +107,15 @@ class AbstractFormIO
/**
* Get the IO value.
*
* @param string $sEventType
* @param string|null $sEventType
*
* @return mixed
*/
public function GetValue(string $sEventType): mixed
public function GetValue(string $sEventType = null): mixed
{
if($sEventType === null) {
return $this->Value();
}
return $this->aValues[$sEventType] ?? null;
}
@@ -123,10 +126,22 @@ class AbstractFormIO
*/
public function HasValue(): bool
{
$PostSetDataExist = array_key_exists(FormEvents::POST_SET_DATA, $this->aValues) && $this->aValues[FormEvents::POST_SET_DATA] !== null;
$PostSubmitExist = array_key_exists(FormEvents::POST_SUBMIT, $this->aValues) && $this->aValues[FormEvents::POST_SUBMIT] !== null;
return $this->HasEventValue(FormEvents::POST_SET_DATA) || $this->HasEventValue(FormEvents::POST_SUBMIT);
}
return $PostSetDataExist || $PostSubmitExist;
/**
* 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;
}
/**
@@ -158,7 +173,7 @@ class AbstractFormIO
*
* @return mixed
*/
public function Value(): mixed
private function Value(): mixed
{
if (array_key_exists(FormEvents::POST_SUBMIT, $this->aValues)) {
return $this->aValues[FormEvents::POST_SUBMIT];

View File

@@ -29,7 +29,7 @@ class OqlToClassConverter extends AbstractConverter
if (isset($aMatches[1])) {
$sSelectedClass = $aMatches[1];
if (!MetaModel::IsValidClass($sSelectedClass)) {
throw new IOException('Incorrect OQL select class name '.$sSelectedClass);
throw new IOException("Class `$sSelectedClass` not found");
}
return new ClassIOFormat($aMatches[1]);

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\Block\IO\Converter;
use Combodo\iTop\Forms\Block\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

@@ -19,6 +19,15 @@ class FormInput extends AbstractFormIO
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.

View File

@@ -17,7 +17,6 @@ class FormOutput extends AbstractFormIO
/** @var AbstractConverter|null */
private null|AbstractConverter $oConverter;
/** @var array */
private array $aBindingsToOutputs = [];
@@ -113,4 +112,9 @@ class FormOutput extends AbstractFormIO
{
return $this->aBindingsToInputs;
}
public function HasBindings(): bool
{
return count($this->aBindingsToInputs) > 0;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Combodo\iTop\Forms\Block\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

@@ -7,8 +7,6 @@
namespace Combodo\iTop\Forms\FormBuilder;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
@@ -55,10 +53,10 @@ class DependencyHandler
// Add form ready listener
$this->AddFormReadyListener();
// Check the dependencies
// Check the dependencies (handle internal binding)
$this->CheckDependencies($this->oFormBuilder);
// Store the dependency handler
// Store the dependency handler (debug purpose)
self::$aDependencyHandlers[] = $this;
}
@@ -86,7 +84,7 @@ class DependencyHandler
$this->oFormBuilder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
/** Iterate throw listened blocks */
foreach ($this->oDependenciesMap->GetListenedOutputBlockNames() as $sOutputBlockName) {
foreach ($this->oDependenciesMap->GetInitialBoundOutputBlockNames() as $sOutputBlockName) {
// inner binding
if ($sOutputBlockName === $this->oFormBlock->getName()) {
@@ -133,12 +131,10 @@ class DependencyHandler
$oFormBlock = $this->aSubBlocks[$oForm->getName()];
// Compute the block outputs with the data
if (!$oFormBlock instanceof FormBlock) {
$oFormBlock->ComputeOutputs($sEventType, $oEvent->getData());
}
$oFormBlock->ComputeOutputs($sEventType, $oForm->getData());
// Check dependencies
$this->CheckDependencies($oForm->getParent());
$this->CheckDependencies($oForm->getParent(), $oForm->getName(), $sEventType);
};
}
@@ -146,18 +142,32 @@ class DependencyHandler
/**
* @param FormInterface|FormBuilderInterface $oForm
* @param string|null $sOutputBlock
* @param string|null $sEventType
*
* @return void
*/
private function CheckDependencies(FormInterface|FormBuilderInterface $oForm): void
private function CheckDependencies(FormInterface|FormBuilderInterface $oForm, string $sOutputBlock = null, string $sEventType = null): void
{
$aImpactedBlocks = $this->aDependentBlocks;
if($sOutputBlock !== null){
$aImpactedBlocks = $this->oDependenciesMap->GetBlocksDependingOn($sOutputBlock);
}
if($aImpactedBlocks === null){
return;
}
/** Iterate throw dependencies... @var AbstractFormBlock $oDependentBlock */
foreach ($this->aDependentBlocks as $qBlockName => $oDependentBlock) {
// When dependencies met, add the dependent field if not already done
if (!$oDependentBlock->IsAdded() && $oDependentBlock->IsInputsDataReady()) {
foreach ($aImpactedBlocks as $qBlockName => $oDependentBlock) {
// When dependencies met, add the dependent field if not already done or options changed
if ($oDependentBlock->IsVisible($sEventType) && $oDependentBlock->IsInputsDataReady($sEventType)) {
// Get the dependent field options
$aOptions = $oDependentBlock->UpdateOptions();
$oDependentBlock->UpdateDynamicOptions($sEventType);
$aOptions = $oDependentBlock->GetOptionsMergedWithDynamic($sEventType);
// Add the listener callback to the dependent field if it is also a dependency for another field
if ($this->oDependenciesMap->IsTheBlockInDependencies($oDependentBlock->getName())) {
@@ -168,7 +178,7 @@ class DependencyHandler
]);
}
if ($oDependentBlock->AllowAdd()) {
if ($oDependentBlock->AllowAdd($sEventType)) {
$this->aDebugData[] = [
'builder' => $this->oFormBuilder->getName(),
@@ -180,7 +190,7 @@ class DependencyHandler
if (array_key_exists('builder_listener', $aOptions)) {
$this->aDebugData[] = [
'builder' => $this->oFormBuilder->getName(),
'event' => 'form.listen',
'event' => 'form.listen.after',
'form' => $oDependentBlock->getName(),
'value' => 'NA',
];
@@ -196,11 +206,16 @@ class DependencyHandler
}
if ($oDependentBlock->IsAdded() && !$oDependentBlock->IsInputsDataReady()) {
$oForm->add($oDependentBlock->GetName(), HiddenType::class, [
'form_block' => $oDependentBlock,
'prevent_form_build' => true,
]);
if ($oDependentBlock->IsAdded() && (!$oDependentBlock->IsVisible($sEventType) || !$oDependentBlock->IsInputsDataReady($sEventType))) {
$oForm->remove($oDependentBlock->GetName());
$oDependentBlock->SetAdded(false);
$this->aDebugData[] = [
'builder' => $this->oFormBuilder->getName(),
'event' => 'form.remove',
'form' => $oDependentBlock->getName(),
'value' => 'NA'
];
}
}

View File

@@ -6,6 +6,8 @@
namespace Combodo\iTop\Forms\FormBuilder;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\FormBlock;
use Combodo\iTop\Forms\Block\IO\FormBinding;
use Combodo\iTop\Forms\Block\IO\FormInput;
use Combodo\iTop\Forms\Block\IO\FormOutput;
@@ -16,22 +18,25 @@ use Combodo\iTop\Forms\Block\IO\FormOutput;
*/
class DependencyMap
{
/** @var array output to outputs map map */
private array $aOutputToInputsMap = [];
/** @var array array of blocks with dependencies group by dependence */
private array $aBlocksWithDependenciesGroupByDependence = [];
/** @var array input to inputs map map */
private array $aInputToInputsMap = [];
/** @var array array of binding (OUT > OUT) grouped by block and output name */
private array $aBindingsOutputToInput = [];
/** @var array output to outputs */
private array $aOutputToOutputsMap = [];
/** @var array array of binding (IN > IN) grouped by block and output name */
private array $aBindingsInputToInput = [];
/** @var array array of binding (OUT > OUT) grouped by block and output name */
private array $aBindingsOutputToOutputs = [];
private readonly array $aDependentBlocks;
/**
* Constructor.
*
* @param array $aDependentBlocks
* @param array $aBlocksWithDependencies
*/
public function __construct(array $aDependentBlocks)
public function __construct(array $aBlocksWithDependencies)
{
$this->aDependentBlocks = $aDependentBlocks;
// Initialization
@@ -45,21 +50,23 @@ class DependencyMap
*/
private function Init(): void
{
/** Iterate throw blocks with dependencies... @var \Combodo\iTop\Forms\Block\Base\FormBlock $oDependentBlock */
foreach ($this->aDependentBlocks as $sBlockName => $oDependentBlock) {
/** Iterate throw blocks with dependencies... @var AbstractFormBlock $oDependentBlock */
foreach ($this->aBlocksWithDependencies as $oDependentBlock) {
/** Iterate throw the block inputs bindings... @var FormBinding $oBinding * */
foreach ($oDependentBlock->GetInputsBindings() as $oBinding) {
// Output to inputs map
// OUT > IN
if ($oBinding->oSourceIO instanceof FormOutput
&& $oBinding->oDestinationIO instanceof FormInput) {
$this->AddBindingToMap($this->aOutputToInputsMap, $oBinding);
$this->AddBindingToMap($this->aBindingsOutputToInput, $oBinding);
$this->AddToBlockWithDependenciesMap($oBinding->oSourceIO->GetOwnerBlock()->GetName(), $oDependentBlock);
}
// Input to inputs map
// IN > IN
if ($oBinding->oSourceIO instanceof FormInput
&& $oBinding->oDestinationIO instanceof FormInput) {
$this->AddBindingToMap($this->aInputToInputsMap, $oBinding);
$this->AddBindingToMap($this->aBindingsInputToInput, $oBinding);
}
}
@@ -67,16 +74,45 @@ class DependencyMap
/** Iterate throw the block inputs connections... @var FormBinding $oBinding * */
foreach ($oDependentBlock->GetOutputBindings() as $oBinding) {
// Output to outputs map
// OUT > OUT
if ($oBinding->oSourceIO instanceof FormOutput
&& $oBinding->oDestinationIO instanceof FormOutput) {
$this->AddBindingToMap($this->aOutputToOutputsMap, $oBinding);
$this->AddBindingToMap($this->aBindingsOutputToOutputs, $oBinding);
}
}
}
return;
}
/**
* @param string $sDependenceBlockName
* @param AbstractFormBlock $oBlockWithDependencies
*
* @return void
*/
private function AddToBlockWithDependenciesMap(string $sDependenceBlockName, AbstractFormBlock $oBlockWithDependencies): void
{
// Initialize array for this dependence
if(!array_key_exists($sDependenceBlockName, $this->aBlocksWithDependenciesGroupByDependence)){
$this->aBlocksWithDependenciesGroupByDependence[$sDependenceBlockName] = [];
}
// Add the block
$this->aBlocksWithDependenciesGroupByDependence[$sDependenceBlockName][$oBlockWithDependencies->GetName()] = $oBlockWithDependencies;
}
/**
* @param string $sBlockName
*
* @return array|null
*/
public function GetBlocksDependingOn(string $sBlockName): ?array
{
if(!array_key_exists($sBlockName,$this->aBlocksWithDependenciesGroupByDependence)){
return null;
}
return $this->aBlocksWithDependenciesGroupByDependence[$sBlockName] ?? null;
}
/**
@@ -108,12 +144,15 @@ class DependencyMap
/**
* @return array
*/
public function GetListenedOutputBlockNames(): array
public function GetInitialBoundOutputBlockNames(): array
{
$aResult = [];
foreach (array_keys($this->aOutputToInputsMap) as $sOutputBlockName) {
if (!array_key_exists($sOutputBlockName, $this->aDependentBlocks)) {
// Iterate throw binding OUT > IN
foreach (array_keys($this->aBindingsOutputToInput) as $sOutputBlockName) {
// Exclude block containing dependencies
if (!array_key_exists($sOutputBlockName, $this->aBlocksWithDependencies)) {
$aResult[] = $sOutputBlockName;
}
}
@@ -143,7 +182,7 @@ class DependencyMap
*/
public function IsBlockHasOutputs(string $sBlockName): bool
{
return array_key_exists($sBlockName, $this->aOutputToInputsMap);
return array_key_exists($sBlockName, $this->aBindingsOutputToInput);
}
/**
@@ -153,37 +192,39 @@ class DependencyMap
*/
public function GetOutputsForBlock(string $sBlockName): array
{
return array_keys($this->aOutputToInputsMap[$sBlockName]);
return array_keys($this->aBindingsOutputToInput[$sBlockName]);
}
public function GetOutputsDependenciesForBlock(string $sOutputBlockName): array
{
return $this->aOutputToInputsMap[$sOutputBlockName];
return $this->aBindingsOutputToInput[$sOutputBlockName];
}
public function IsTheBlockInDependencies(string $sBlockName): bool
{
foreach ($this->aDependentBlocks as $oDependentBlock) {
if ($oDependentBlock->getName() === $sBlockName) {
return true;
}
}
return false;
// foreach ($this->aDependentBlocks as $oDependentBlock)
// {
// if($oDependentBlock->getName() === $sBlockName) {
// return true;
// }
// }
//
// return false;
return $this->GetBlocksDependingOn($sBlockName) !== null;
}
public function GetOutputToInputs(): array
{
return $this->aOutputToInputsMap;
return $this->aBindingsOutputToInput;
}
public function GetInputToInputs(): array
{
return $this->aInputToInputsMap;
return $this->aBindingsInputToInput;
}
public function GetOutputToOutputs(): array
{
return $this->aOutputToOutputsMap;
return $this->aBindingsOutputToOutputs;
}
}

View File

@@ -13,7 +13,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormConfigInterface;
use Symfony\Component\Form\FormFactoryInterface;
@@ -32,7 +31,7 @@ class FormBuilder implements FormBuilderInterface, IteratorAggregate
private AbstractFormBlock $oFormBlock;
/** @var array sub blocks */
private array $aSubFormBlocks = [];
private array $aChildren = [];
private readonly FormBuilderInterface $builder;
/**
@@ -68,26 +67,26 @@ class FormBuilder implements FormBuilderInterface, IteratorAggregate
return;
}
$aDependentBlocks = [];
$aBlocksWithDependencies = [];
/** Iterate throw the form sub blocks... @var FormBlock $oSubFormBlock */
foreach ($oFormBlock->GetChildren() as $oSubFormBlock) {
foreach ($oFormBlock->GetChildren() as $sBlockName => $oChildBlock) {
// Add to the sub blocks array
$this->aSubFormBlocks[$oSubFormBlock->getName()] = $oSubFormBlock;
// Add to the children
$this->aChildren[$sBlockName] = $oChildBlock;
// Handle sub block
$bHasDependency = $this->HandleSubBlock($oSubFormBlock);
// Handle block
$bHasDependency = $this->HandleSubBlock($oChildBlock);
// Add to the dependencies array
// Add to the array of blocks with dependencies
if ($bHasDependency) {
$aDependentBlocks[$oSubFormBlock->GetName()] = $oSubFormBlock;
$aBlocksWithDependencies[$sBlockName] = $oChildBlock;
}
}
// Create a dependency handler if needed
if (count($aDependentBlocks) > 0) {
$this->oDependencyHandler = new DependencyHandler($this->builder->getName(), $oFormBlock, $this, $this->aSubFormBlocks, $aDependentBlocks);
if (count($aBlocksWithDependencies) > 0) {
$this->oDependencyHandler = new DependencyHandler($this->builder->getName(), $oFormBlock, $this, $this->aChildren, $aBlocksWithDependencies);
}
}
@@ -104,19 +103,19 @@ class FormBuilder implements FormBuilderInterface, IteratorAggregate
// Has dependencies blocks
if ($oSubFormBlock->HasDependenciesBlocks()) {
// Insert a hidden type to save the place
$this->builder->add($oSubFormBlock->GetName(), HiddenType::class, [
'form_block' => $oSubFormBlock,
'prevent_form_build' => true,
// 'mapped' => false,
// 'disabled' => true,
]);
// // Insert a hidden type to save the place
// $this->builder->add($oSubFormBlock->GetName(), HiddenType::class, [
// 'form_block' => $oSubFormBlock,
// 'prevent_form_build' => true,
//// 'mapped' => false,
//// 'disabled' => true,
// ]);
return true;
} else {
// Directly insert the block corresponding form type
$this->add($oSubFormBlock->GetName(), $oSubFormBlock->GetFormType(), $oSubFormBlock->UpdateOptions());
$this->add($oSubFormBlock->GetName(), $oSubFormBlock->GetFormType(), $oSubFormBlock->GetOptions());
$oSubFormBlock->SetAdded(true);
return false;
@@ -125,15 +124,15 @@ class FormBuilder implements FormBuilderInterface, IteratorAggregate
}
/**
* Get a sub form block.
* Get a child block.
*
* @param string $sName
*
* @return AbstractFormBlock|null
*/
public function GetSubFormBlock(string $sName): ?AbstractFormBlock
public function GetChild(string $sName): ?AbstractFormBlock
{
return $this->aSubFormBlocks[$sName] ?? null;
return $this->aChildren[$sName] ?? null;
}
/**

View File

@@ -0,0 +1,8 @@
<?php
namespace Combodo\iTop\Forms;
interface IFormBlock
{
}

View File

@@ -3,13 +3,28 @@
{# Widgets #}
{%- block widget_attributes -%}
{{- parent() -}}
{% if trigger_form_submit_on_modify %}
onChange="this.form.setAttribute('novalidate', true); this.form.requestSubmit();console.log('Auto submitting form due to change in field {{ full_name }}');"
{% endif %}
{%- endblock widget_attributes -%}
{%- block form_widget_simple -%}
{% if type == 'text' %}{% set ibo_class='ibo-input-string' %}{% else %}{% set ibo_class='ibo-input-' ~ type %}{% endif %}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' ibo-input ' ~ ibo_class)|trim}) %}
{{- parent() -}}
{% if trigger_form_submit_on_modify %}
onChange="this.form.requestSubmit();console.log('Auto submitting form due to change in field {{ full_name }}');"
{% endif %}
{%- endblock widget_attributes -%}
{%- endblock form_widget_simple -%}
{%- block textarea_widget -%}
{% if type == 'text' %}{% set ibo_class='ibo-input-string' %}{% else %}{% set ibo_class='ibo-input-' ~ type %}{% endif %}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' ibo-input ' ~ ibo_class)|trim}) %}
{{- parent() -}}
{%- endblock textarea_widget -%}
{%- block choice_widget_collapsed -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' ibo-input')|trim}) %}
{{- parent() -}}
{%- endblock choice_widget_collapsed -%}
{%- block form_label -%}
{%- if compound is defined and compound -%}
@@ -20,13 +35,31 @@
{{- parent() -}}
{%- endblock form_label -%}
{%- block form_rows -%}
{% for sId,child in form|filter(child => not child.rendered) %}
<div id="block_{{ sId }}" class="ibo-field ibo-content-block ibo-block ibo-field-small">
{{ form_row(child) }}
{#{%- block form_rows -%}#}
{# {% for block in blocks %}#}
{# <div id="block_{{ block.name }}" class="ibo-field ibo-content-block ibo-block ibo-field-small">#}
{# {{ block.name }} {{ block.added }}#}
{# {% if block.added == 1 %}#}
{# {{ form_row(form[block.name]) }}#}
{# {% endif %}#}
{# </div>#}
{# {% endfor %}#}
{#{%- endblock form_rows -%}#}
{%- block collection_widget -%}
{% if prototype is defined and not prototype.rendered %}
{%- set attr = attr|merge({'data-prototype': form_row(prototype), 'class': name, 'data-index': form|length > 0 ? form|last.vars.name + 1 : 0 }) -%}
{% endif %}
{{- block('form_widget') -}}
{% if allow_add %}
<div style="margin-top: 20px;">
<button type="button" class="add_item_link ibo-button ibo-button ibo-is-regular " data-collection-holder-class="{{ name }}">{{ button_label|dict_s }}</button>
</div>
{% endfor %}
{%- endblock form_rows -%}
{% endif %}
{%- endblock collection_widget -%}
{%- block form_label_content -%}
{{- parent() -}}

View File

@@ -0,0 +1,15 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Forms;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class Binding extends ItopDataTestCase
{
}

View File

@@ -0,0 +1,92 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Forms;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\Base\CheckboxFormBlock;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\Base\TextFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Forms;
use Combodo\iTop\Forms\IFormBlock;
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use OutOfBoundsException;
use ReflectionException;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class Block extends ItopDataTestCase
{
/**
* @throws ReflectionException
*/
public function testBlockFormType()
{
$oChoiceBlock = new ChoiceFormBlock('choiceBlock');
$test = new \ReflectionClass($oChoiceBlock->GetFormType());
$this->assertTrue($test->isSubclassOf(AbstractType::class));
$oFormBlock = new ChoiceFormBlock('formBlock');
$test = new \ReflectionClass($oFormBlock->GetFormType());
$this->assertTrue($test->isSubclassOf(AbstractType::class));
}
/**
* Pass a Symfony type instead of a FormBlock type will raise an exception
*
* @throws ReflectionException
*/
public function testAddBlockFromSymfonyType()
{
$oFormBlock = new FormBlock('formBlock');
$this->expectException(FormBlockException::class);
$oFormBlock->Add('wrong', TextType::class, []);
}
/**
* All block may contain a reference to themselves in their options
*/
public function testBlockOptionsContainsBlockReference()
{
$aFormBlocks = InterfaceDiscovery::GetInstance()->FindItopClasses(iFormBlock::class);
foreach ($aFormBlocks as $sFormBlock) {
$oChoiceBlock = new($sFormBlock)($sFormBlock);
$this->assertTrue($oChoiceBlock->GetOptions()['form_block'] === $oChoiceBlock);
}
}
/**
* Dependent fields are not added to the form directly.
*
* @return void
* @throws FormBlockException
* @throws ReflectionException
*/
public function testFormBlockNotContainsDependentFields()
{
// form with a dependent field
$oFormBlock = new FormBlock('formBlock');
$oFormBlock->Add('firstname', TextFormBlock::class, []);
$oFormBlock->Add('lastname', TextFormBlock::class, []);
$oFormBlock->Add('allow_age', CheckboxFormBlock::class, []);
$oFormBlock->Add('birthdate', TextFormBlock::class, [])
->DependsOn(AbstractFormBlock::INPUT_VISIBLE, 'allow_age', CheckboxFormBlock::OUTPUT_CHECKED);
// form builder
$oFormFactoryBuilder = Forms::createFormFactoryBuilder();
$oForm = $oFormFactoryBuilder->getFormFactory()->createNamedBuilder($oFormBlock->GetName(), $oFormBlock->GetFormType(), [], $oFormBlock->GetOptions())->getForm();
// try to get the dependent field
$this->expectException(OutOfBoundsException::class);
$oForm->get('birthdate');
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Forms;
use Combodo\iTop\Forms\Block\Base\CheckboxFormBlock;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\Base\TextFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ReflectionException;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class BlockIO extends ItopDataTestCase
{
}

View File

@@ -0,0 +1,15 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Forms;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class Builder extends ItopDataTestCase
{
}