diff --git a/application/datamodel.application.xml b/application/datamodel.application.xml
index e20a1a95b..e8674d7f9 100644
--- a/application/datamodel.application.xml
+++ b/application/datamodel.application.xml
@@ -562,7 +562,70 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/startup.inc.php b/application/startup.inc.php
index 4e033fc27..fe4ba33c2 100644
--- a/application/startup.inc.php
+++ b/application/startup.inc.php
@@ -42,6 +42,7 @@ register_shutdown_function(function()
$sReservedMemory = null;
if (!is_null($err = error_get_last()) && ($err['type'] == E_ERROR))
{
+ var_export($err);
// Remove stack trace from MySQLException (since 2.7.2 see N°3174)
$sMessage = $err['message'];
if (strpos($sMessage, 'MySQLException') !== false) {
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index 8c7dad6c2..87e679229 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -430,6 +430,12 @@ return array(
'Combodo\\iTop\\Dependencies\\NPM\\iTopNPM' => $baseDir . '/sources/Dependencies/NPM/iTopNPM.php',
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php',
+ 'Combodo\\iTop\\FormType\\Base\\HiddenType' => $baseDir . '/sources/FormType/Base/HiddenType.php',
+ 'Combodo\\iTop\\FormType\\Base\\TextType' => $baseDir . '/sources/FormType/Base/TextType.php',
+ 'Combodo\\iTop\\FormType\\Base\\TextareaType' => $baseDir . '/sources/FormType/Base/TextareaType.php',
+ 'Combodo\\iTop\\FormType\\Orm\\AttCodeGroupByType' => $baseDir . '/sources/FormType/Orm/AttCodeGroupByType.php',
+ 'Combodo\\iTop\\FormType\\Orm\\QueryType' => $baseDir . '/sources/FormType/Orm/QueryType.php',
+ 'Combodo\\iTop\\FormType\\Orm\\ValuesFromAttcodeType' => $baseDir . '/sources/FormType/Orm/ValuesFromAttcodeType.php',
'Combodo\\iTop\\Form\\Field\\AbstractSimpleField' => $baseDir . '/sources/Form/Field/AbstractSimpleField.php',
'Combodo\\iTop\\Form\\Field\\BlobField' => $baseDir . '/sources/Form/Field/BlobField.php',
'Combodo\\iTop\\Form\\Field\\CaseLogField' => $baseDir . '/sources/Form/Field/CaseLogField.php',
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index e8dbf5f91..8185589d3 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -785,6 +785,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Dependencies\\NPM\\iTopNPM' => __DIR__ . '/../..' . '/sources/Dependencies/NPM/iTopNPM.php',
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
+ 'Combodo\\iTop\\FormType\\Base\\HiddenType' => __DIR__ . '/../..' . '/sources/FormType/Base/HiddenType.php',
+ 'Combodo\\iTop\\FormType\\Base\\TextType' => __DIR__ . '/../..' . '/sources/FormType/Base/TextType.php',
+ 'Combodo\\iTop\\FormType\\Base\\TextareaType' => __DIR__ . '/../..' . '/sources/FormType/Base/TextareaType.php',
+ 'Combodo\\iTop\\FormType\\Orm\\AttCodeGroupByType' => __DIR__ . '/../..' . '/sources/FormType/Orm/AttCodeGroupByType.php',
+ 'Combodo\\iTop\\FormType\\Orm\\QueryType' => __DIR__ . '/../..' . '/sources/FormType/Orm/QueryType.php',
+ 'Combodo\\iTop\\FormType\\Orm\\ValuesFromAttcodeType' => __DIR__ . '/../..' . '/sources/FormType/Orm/ValuesFromAttcodeType.php',
'Combodo\\iTop\\Form\\Field\\AbstractSimpleField' => __DIR__ . '/../..' . '/sources/Form/Field/AbstractSimpleField.php',
'Combodo\\iTop\\Form\\Field\\BlobField' => __DIR__ . '/../..' . '/sources/Form/Field/BlobField.php',
'Combodo\\iTop\\Form\\Field\\CaseLogField' => __DIR__ . '/../..' . '/sources/Form/Field/CaseLogField.php',
diff --git a/sources/Application/TwigBase/Controller/Controller.php b/sources/Application/TwigBase/Controller/Controller.php
index 90c1549c8..718a7f74c 100644
--- a/sources/Application/TwigBase/Controller/Controller.php
+++ b/sources/Application/TwigBase/Controller/Controller.php
@@ -697,6 +697,9 @@ abstract class Controller extends AbstractController
public function GetForm(string $type = FormType::class, mixed $data = null, array $options = []): FormInterface
{
+ if (is_null($data)) {
+ $data = $type::GetDefaultData();
+ }
return $this->GetFormBuilder($type, $data,$options)->getForm();
}
diff --git a/sources/FormType/Base/HiddenType.php b/sources/FormType/Base/HiddenType.php
new file mode 100644
index 000000000..cca49a178
--- /dev/null
+++ b/sources/FormType/Base/HiddenType.php
@@ -0,0 +1,23 @@
+add('hidden', HiddenType::class, ['mapped' => false]);
+ $sRelatedNode = $options['query_source'];
+ $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($sRelatedNode): void {
+ $oForm = $event->getForm();
+ $sCurrentValue = $oForm->getParent()->get($sRelatedNode)->getData();
+ $this->BuildSubField($oForm, $sCurrentValue);
+ });
+
+ $builder->get('hidden')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($sRelatedNode): void {
+ $oForm = $event->getForm()->getParent();
+ $sCurrentValue = $oForm->getParent()->get($sRelatedNode)->getData();
+ $this->BuildSubField($oForm, $sCurrentValue);
+ });
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ parent::configureOptions($resolver);
+ $resolver->setRequired('query_source');
+ $resolver->setAllowedTypes('query_source', 'string');
+ }
+
+ public function BuildSubField(FormInterface $oForm, string $sQuery): void
+ {
+ $aData = $oForm->getParent()->getData();
+ \IssueLog::Info('Form Data: '.var_export($aData, true));
+
+ //$aFormOptions['inherit_data'] = true;
+ $aFormOptions['choices'] = $this->GetGroupByOptions($sQuery);
+ $aFormOptions['multiple'] = false;
+
+ // create the field, this is similar the $builder->add()
+ // field name, field type, field options
+ $oForm->add('selected', SymfonyChoiceType::class, $aFormOptions);
+ }
+
+ protected $oModelReflection;
+
+ protected function GetGroupByOptions($sOql)
+ {
+ $this->oModelReflection = new \ModelReflectionRuntime();
+
+ $aGroupBy = array();
+ try
+ {
+ $oQuery = $this->oModelReflection->GetQuery($sOql);
+ $sClass = $oQuery->GetClass();
+ foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
+ {
+ // For external fields, find the real type of the target
+ $sExtFieldAttCode = $sAttCode;
+ $sTargetClass = $sClass;
+ while (is_a($sAttType, 'AttributeExternalField', true))
+ {
+ $sExtKeyAttCode = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtFieldAttCode, 'extkey_attcode');
+ $sTargetAttCode = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtFieldAttCode, 'target_attcode');
+ $sTargetClass = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtKeyAttCode, 'targetclass');
+ $aTargetAttCodes = $this->oModelReflection->ListAttributes($sTargetClass);
+ $sAttType = $aTargetAttCodes[$sTargetAttCode];
+ $sExtFieldAttCode = $sTargetAttCode;
+ }
+
+ $aForbidenAttType = [
+ 'AttributeLinkedSet',
+ 'AttributeFriendlyName',
+
+ 'iAttributeNoGroupBy', //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
+ 'AttributeOneWayPassword',
+ 'AttributeEncryptedString',
+ 'AttributePassword',
+ ];
+ foreach ($aForbidenAttType as $sForbidenAttType) {
+ if (is_a($sAttType, $sForbidenAttType, true))
+ {
+ continue 2;
+ }
+ }
+
+ $sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
+ if (!in_array($sLabel, $aGroupBy))
+ {
+ $aGroupBy[$sAttCode] = $sLabel;
+
+ if (is_a($sAttType, 'AttributeDateTime', true))
+ {
+ $aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
+ $aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month', $sLabel);
+ $aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek', $sLabel);
+ $aGroupBy[$sAttCode.':day_of_month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth', $sLabel);
+ }
+ }
+ }
+ asort($aGroupBy);
+ }
+ catch (Exception $e)
+ {
+ // Fallback in case of OQL problem
+ }
+ return array_flip($aGroupBy);
+ }
+}
\ No newline at end of file
diff --git a/sources/FormType/Orm/QueryType.php b/sources/FormType/Orm/QueryType.php
new file mode 100644
index 000000000..ec1e852d9
--- /dev/null
+++ b/sources/FormType/Orm/QueryType.php
@@ -0,0 +1,30 @@
+setDefault('attr', ['class' => 'ibo-is-code ibo-query-oql']);
+ }
+
+ public function getBlockPrefix(): string
+ {
+ return 'itop_query';
+ }
+}
\ No newline at end of file
diff --git a/sources/FormType/Orm/ValuesFromAttcodeType.php b/sources/FormType/Orm/ValuesFromAttcodeType.php
new file mode 100644
index 000000000..6881360e9
--- /dev/null
+++ b/sources/FormType/Orm/ValuesFromAttcodeType.php
@@ -0,0 +1,62 @@
+add('hidden', HiddenType::class, ['mapped' => false]);
+ $sAttCodeType = $options['attcode_source'];
+ $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($sAttCodeType): void {
+ $oForm = $event->getForm();
+ $sAttCode = $oForm->getParent()->get($sAttCodeType)->get('selected')->getData();
+ $this->BuildSubField($oForm, $sAttCode);
+ });
+
+ $builder->get('hidden')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($sAttCodeType): void {
+ $oForm = $event->getForm()->getParent();
+ $sAttCode = $oForm->getParent()->get($sAttCodeType)->get('selected')->getData();
+ $this->BuildSubField($oForm, $sAttCode);
+ });
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ parent::configureOptions($resolver);
+ $resolver->setRequired('attcode_source');
+ $resolver->setAllowedTypes('attcode_source', 'string');
+ }
+
+ public function BuildSubField(FormInterface $oForm, string $sAttCode): void
+ {
+ $aData = $oForm->getParent()->getData();
+ \IssueLog::Info('Form Data: '.var_export($aData, true));
+
+ $sQuery = $aData['query'];
+ $sClass = \DBSearch::FromOQL($sQuery)->GetClass();
+ $oAttDef = \MetaModel::GetAttributeDef($sClass, $sAttCode);
+
+ //$aFormOptions['inherit_data'] = true;
+ $aFormOptions['choices'] = array_flip($oAttDef->GetAllowedValues());
+ $aFormOptions['multiple'] = true;
+
+ // create the field, this is similar the $builder->add()
+ // field name, field type, field options
+ $oForm->add('selected', SymfonyChoiceType::class, $aFormOptions);
+ }
+
+}
\ No newline at end of file
diff --git a/tests/php-unit-tests/unitary-tests/application/FormType/Base/TextTypeTest.php b/tests/php-unit-tests/unitary-tests/application/FormType/Base/TextTypeTest.php
new file mode 100644
index 000000000..0dba343ed
--- /dev/null
+++ b/tests/php-unit-tests/unitary-tests/application/FormType/Base/TextTypeTest.php
@@ -0,0 +1,37 @@
+addExtension(new HttpFoundationExtension())
+ ->getFormFactory();
+ return $oFormFactory->createBuilder($type, $data,$options);
+ }
+
+ public function GetForm(string $type = FormType::class, mixed $data = null, array $options = []): FormInterface
+ {
+ return $this->GetFormBuilder($type, $data,$options)->getForm();
+ }
+
+ public function testTextType()
+ {
+ $oFormView = $this->GetForm(AttCodeGroupByType::class)->createView();
+ return;
+ }
+}