mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-24 11:08:45 +02:00
Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager
This commit is contained in:
@@ -14,6 +14,8 @@ function triggerTurbo(el) {
|
||||
el.form.querySelector(`[name="${sFormName}[_turbo_trigger]"]`).value = el.getAttribute('name');
|
||||
el.form.setAttribute('novalidate', true);
|
||||
el.form.requestSubmit();
|
||||
el.form.querySelector(`[name="${sFormName}[_turbo_trigger]"]`).value = null;
|
||||
el.form.setAttribute('novalidate', false);
|
||||
$aFormBlockDataTransmittedData[name] = el.value;
|
||||
}
|
||||
|
||||
|
||||
@@ -534,6 +534,8 @@ return array(
|
||||
'Combodo\\iTop\\Forms\\Register\\OptionsRegister' => $baseDir . '/sources/Forms/Register/OptionsRegister.php',
|
||||
'Combodo\\iTop\\Forms\\Register\\RegisterException' => $baseDir . '/sources/Forms/Register/RegisterException.php',
|
||||
'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => $baseDir . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php',
|
||||
'Combodo\\iTop\\Forms\\Validator\\AttributeExist' => $baseDir . '/sources/Forms/Validator/AttributeExist.php',
|
||||
'Combodo\\iTop\\Forms\\Validator\\AttributeExistValidator' => $baseDir . '/sources/Forms/Validator/AttributeExistValidator.php',
|
||||
'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => $baseDir . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php',
|
||||
'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php',
|
||||
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => $baseDir . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php',
|
||||
@@ -3343,7 +3345,6 @@ return array(
|
||||
'Symfony\\Component\\Validator\\Mapping\\PropertyMetadataInterface' => $vendorDir . '/symfony/validator/Mapping/PropertyMetadataInterface.php',
|
||||
'Symfony\\Component\\Validator\\Mapping\\TraversalStrategy' => $vendorDir . '/symfony/validator/Mapping/TraversalStrategy.php',
|
||||
'Symfony\\Component\\Validator\\ObjectInitializerInterface' => $vendorDir . '/symfony/validator/ObjectInitializerInterface.php',
|
||||
'Symfony\\Component\\Validator\\Test\\ConstraintValidatorTestCase' => $vendorDir . '/symfony/validator/Test/ConstraintValidatorTestCase.php',
|
||||
'Symfony\\Component\\Validator\\Util\\PropertyPath' => $vendorDir . '/symfony/validator/Util/PropertyPath.php',
|
||||
'Symfony\\Component\\Validator\\Validation' => $vendorDir . '/symfony/validator/Validation.php',
|
||||
'Symfony\\Component\\Validator\\ValidatorBuilder' => $vendorDir . '/symfony/validator/ValidatorBuilder.php',
|
||||
@@ -3893,5 +3894,5 @@ return array(
|
||||
'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php',
|
||||
'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php',
|
||||
'utils' => $baseDir . '/application/utils.inc.php',
|
||||
'©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
'<EFBFBD>' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
);
|
||||
|
||||
@@ -920,6 +920,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Combodo\\iTop\\Forms\\Register\\OptionsRegister' => __DIR__ . '/../..' . '/sources/Forms/Register/OptionsRegister.php',
|
||||
'Combodo\\iTop\\Forms\\Register\\RegisterException' => __DIR__ . '/../..' . '/sources/Forms/Register/RegisterException.php',
|
||||
'Combodo\\iTop\\Forms\\Twig\\Extension\\FormCompatibilityExtension' => __DIR__ . '/../..' . '/sources/Forms/Twig/Extension/FormCompatibilityExtension.php',
|
||||
'Combodo\\iTop\\Forms\\Validator\\AttributeExist' => __DIR__ . '/../..' . '/sources/Forms/Validator/AttributeExist.php',
|
||||
'Combodo\\iTop\\Forms\\Validator\\AttributeExistValidator' => __DIR__ . '/../..' . '/sources/Forms/Validator/AttributeExistValidator.php',
|
||||
'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => __DIR__ . '/../..' . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php',
|
||||
'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php',
|
||||
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php',
|
||||
@@ -3729,7 +3731,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Symfony\\Component\\Validator\\Mapping\\PropertyMetadataInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/PropertyMetadataInterface.php',
|
||||
'Symfony\\Component\\Validator\\Mapping\\TraversalStrategy' => __DIR__ . '/..' . '/symfony/validator/Mapping/TraversalStrategy.php',
|
||||
'Symfony\\Component\\Validator\\ObjectInitializerInterface' => __DIR__ . '/..' . '/symfony/validator/ObjectInitializerInterface.php',
|
||||
'Symfony\\Component\\Validator\\Test\\ConstraintValidatorTestCase' => __DIR__ . '/..' . '/symfony/validator/Test/ConstraintValidatorTestCase.php',
|
||||
'Symfony\\Component\\Validator\\Util\\PropertyPath' => __DIR__ . '/..' . '/symfony/validator/Util/PropertyPath.php',
|
||||
'Symfony\\Component\\Validator\\Validation' => __DIR__ . '/..' . '/symfony/validator/Validation.php',
|
||||
'Symfony\\Component\\Validator\\ValidatorBuilder' => __DIR__ . '/..' . '/symfony/validator/ValidatorBuilder.php',
|
||||
@@ -4279,7 +4280,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
|
||||
'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
|
||||
'utils' => __DIR__ . '/../..' . '/application/utils.inc.php',
|
||||
'©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
'<EFBFBD>' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
|
||||
@@ -11,6 +11,7 @@ use Combodo\iTop\Forms\Block\AbstractFormBlock;
|
||||
use Combodo\iTop\Forms\Block\FormBlockException;
|
||||
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
|
||||
use Combodo\iTop\Forms\Register\IORegister;
|
||||
use Exception;
|
||||
use Expression;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
|
||||
@@ -52,9 +53,13 @@ abstract class AbstractExpressionFormBlock extends AbstractFormBlock
|
||||
foreach ($aParamsToResolve as $sParamToResolve) {
|
||||
$aResolvedParams[$sParamToResolve] = strval($this->GetInputValue($sParamToResolve));
|
||||
}
|
||||
$aFieldsToResolve = $oExpression->ListRequiredFields();
|
||||
foreach ($aFieldsToResolve as $sFieldToResolve) {
|
||||
$aResolvedParams[$sFieldToResolve] = strval($this->GetInputValue($sFieldToResolve));
|
||||
}
|
||||
return $oExpression->Evaluate($aResolvedParams);
|
||||
} catch (\Exception $e) {
|
||||
throw new FormBlockException('Compute expression '.json_encode($sExpression).' block issue', 0, $e);
|
||||
} catch (Exception $e) {
|
||||
throw new FormBlockException('Compute expression '.json_encode($sExpression).' block issue: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,9 +95,12 @@ class FormBuilder implements FormBuilderInterface, IteratorAggregate
|
||||
$oFormBlock->oDependencyMap = $this->oDependencyHandler->GetMap();
|
||||
}
|
||||
|
||||
if (is_null($oFormBlock->GetParent())) {
|
||||
if ($oFormBlock->IsRootBlock()) {
|
||||
// Insert a hidden type to save the place
|
||||
$this->builder->add('_turbo_trigger', HiddenType::class, ['prevent_form_build' => true]);
|
||||
$this->builder->add('_turbo_trigger', HiddenType::class, [
|
||||
'prevent_form_build' => true,
|
||||
'mapped' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ final class Forms
|
||||
public static function createFormFactoryBuilder(): FormFactoryBuilderInterface
|
||||
{
|
||||
// Set up the Validator component
|
||||
$validator = Validation::createValidator();
|
||||
$validator = Validation::createValidatorBuilder()
|
||||
->enableAttributeMapping()->getValidator();
|
||||
|
||||
return (new FormFactoryBuilder())
|
||||
->addExtension(new HttpFoundationExtension())
|
||||
->addExtension(new ValidatorExtension($validator))
|
||||
|
||||
@@ -19,4 +19,10 @@ use Throwable;
|
||||
*/
|
||||
class FormsException extends Exception
|
||||
{
|
||||
public function __construct(string $sMessage = '', int $iCode = 0, ?Throwable $oPrevious = null, array $aContext = [])
|
||||
{
|
||||
parent::__construct($sMessage, $iCode, $oPrevious);
|
||||
IssueLog::Exception(get_class($this).' occurs: '.$sMessage, $this, null, $aContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class AbstractFormIO
|
||||
public function SetName(string $sName): self
|
||||
{
|
||||
// Check name validity
|
||||
if (preg_match('/(?<name>\w+)/', $sName, $aMatches)) {
|
||||
if (preg_match('/^(?<name>((\w+\.\w+)|\w+))$/', $sName, $aMatches)) {
|
||||
$sParsedName = $aMatches['name'];
|
||||
if ($sParsedName !== $sName) {
|
||||
$sName = json_encode($sName);
|
||||
|
||||
@@ -36,8 +36,6 @@ class OqlToClassConverter extends AbstractConverter
|
||||
$oModelReflection = DIService::GetInstance()->GetService('ModelReflection');
|
||||
try {
|
||||
$oQuery = $oModelReflection->GetQuery($oData);
|
||||
} catch (\OQLParserException $e) {
|
||||
throw new FormBlockIOException($e->GetIssue(), $e->getCode(), $e);
|
||||
} catch (Exception $e) {
|
||||
throw new FormBlockIOException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
68
sources/Forms/Validator/AttributeExist.php
Normal file
68
sources/Forms/Validator/AttributeExist.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Forms\Validator;
|
||||
|
||||
use Attribute;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Attribute exist constraint.
|
||||
*
|
||||
* @package Combodo\iTop\Forms\Validator
|
||||
* @since 3.3.0
|
||||
*/
|
||||
#[Attribute]
|
||||
class AttributeExist extends Constraint
|
||||
{
|
||||
/** @var string Violation message */
|
||||
public string $sMessage = 'The attribute "{{ attribute }}" doesn\'t exist in class "{{ class }}" from OQL "{{ oql }}".';
|
||||
|
||||
/** @var string|mixed OQL expression property path */
|
||||
public string $sOqlPropertyPath;
|
||||
|
||||
/** @var string|null Attribute list filter */
|
||||
public ?string $sFilter;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string|null $sOqlPropertyPath
|
||||
* @param string|null $sFilter
|
||||
* @param array $aOptions
|
||||
* @param array|null $aGroups
|
||||
* @param mixed|null $oPayload
|
||||
*/
|
||||
public function __construct(string $sOqlPropertyPath = null, string $sFilter = null, array $aOptions = [], ?array $aGroups = null, mixed $oPayload = null)
|
||||
{
|
||||
if ($sOqlPropertyPath === null) {
|
||||
throw new InvalidArgumentException('The argument "sOqlPropertyPath" must be set.');
|
||||
}
|
||||
|
||||
// Merge argument into options array
|
||||
$aOptions = array_merge([
|
||||
'sOqlPropertyPath' => $sOqlPropertyPath,
|
||||
], $aOptions);
|
||||
|
||||
parent::__construct($aOptions, $aGroups, $oPayload);
|
||||
|
||||
// Retrieve options
|
||||
$this->sFilter = $sFilter;
|
||||
$this->sOqlPropertyPath = $aOptions['sOqlPropertyPath'];
|
||||
}
|
||||
|
||||
public function getDefaultOption(): string
|
||||
{
|
||||
return 'sOqlPropertyPath';
|
||||
}
|
||||
|
||||
public function getRequiredOptions(): array
|
||||
{
|
||||
return ['sOqlPropertyPath'];
|
||||
}
|
||||
}
|
||||
57
sources/Forms/Validator/AttributeExistValidator.php
Normal file
57
sources/Forms/Validator/AttributeExistValidator.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Forms\Validator;
|
||||
|
||||
use Combodo\iTop\Forms\IO\Converter\OqlToClassConverter;
|
||||
use Combodo\iTop\Service\DependencyInjection\DIService;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Attribute exist validator.
|
||||
*
|
||||
* @package Combodo\iTop\Forms\Validator
|
||||
* @since 3.3.0
|
||||
*/
|
||||
class AttributeExistValidator extends ConstraintValidator
|
||||
{
|
||||
private ?PropertyAccessorInterface $propertyAccessor;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
$sOql = $this->propertyAccessor->getValue($this->context->getObject(), $constraint->sOqlPropertyPath);
|
||||
|
||||
$oOqlToClassConverter = new OqlToClassConverter();
|
||||
$sClass = strval($oOqlToClassConverter->Convert($sOql));
|
||||
|
||||
$sClass = "UserRequest";
|
||||
|
||||
/** List attributes @var ModelReflection $oModelReflection */
|
||||
$oModelReflection = DIService::GetInstance()->GetService('ModelReflection');
|
||||
$aAttributeCodes = array_keys($oModelReflection->ListAttributes($sClass));
|
||||
|
||||
if (!in_array($value, $aAttributeCodes, true)) {
|
||||
$this->context->buildViolation($constraint->sMessage)
|
||||
->setParameter('{{ attribute }}', $value)
|
||||
->setParameter('{{ class }}', $sClass)
|
||||
->setParameter('{{ oql }}', $sOql)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -25,16 +25,16 @@ abstract class AbstractFormsTest extends ItopDataTestCase
|
||||
{
|
||||
public function GivenInput(string $sName, string $sType = StringIOFormat::class): FormInput
|
||||
{
|
||||
$oBlock = $this->GivenFormBlock($sName.'_block');
|
||||
$oBlock = $this->GivenFormBlock($sName);
|
||||
|
||||
return new FormInput($sName.'_input', $sType, $oBlock);
|
||||
return new FormInput($sName, $sType, $oBlock);
|
||||
}
|
||||
|
||||
public function GivenOutput(string $sName, string $sType = StringIOFormat::class): FormOutput
|
||||
{
|
||||
$oBlock = $this->GivenFormBlock($sName.'_block');
|
||||
$oBlock = $this->GivenFormBlock($sName);
|
||||
|
||||
return new FormOutput($sName.'_output', $sType, $oBlock);
|
||||
return new FormOutput($sName, $sType, $oBlock);
|
||||
}
|
||||
|
||||
public function GivenFormBlock(string $sName): FormBlock
|
||||
|
||||
@@ -67,7 +67,7 @@ class AbstractFormIOTest extends AbstractFormsTest
|
||||
* @return void
|
||||
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
|
||||
*/
|
||||
public function testNameFormatSupportsOnlyLettersUnderscoreAndNumbers(string $sName, bool $bGenerateException = true)
|
||||
public function testNameFormatSupportsOnlyLettersUnderscoreAndNumbersAndDot(string $sName, bool $bGenerateException = true)
|
||||
{
|
||||
|
||||
if ($bGenerateException) {
|
||||
@@ -75,7 +75,7 @@ class AbstractFormIOTest extends AbstractFormsTest
|
||||
}
|
||||
$oInput = $this->GivenInput($sName);
|
||||
if (!$bGenerateException) {
|
||||
$this->assertEquals($sName.'_input', $oInput->GetName());
|
||||
$this->assertEquals($sName, $oInput->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,12 +88,15 @@ class AbstractFormIOTest extends AbstractFormsTest
|
||||
'Percent not supported' => ['name%'],
|
||||
'Accent not supported' => ['namé'],
|
||||
'emoji not supported' => ['🎄🎄🎄🎄🎄'],
|
||||
'.name not supported' => ['.name'],
|
||||
'name. not supported' => ['name.'],
|
||||
|
||||
// Corrects
|
||||
'Numbers OK' => ['name123', false],
|
||||
'Starting with number OK' => ['123name123', false],
|
||||
'Underscore OK' => ['The_test_name', false],
|
||||
'Camel OK' => ['TheTestName', false],
|
||||
'name.subname OK' => ['name.subname', false],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user