diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 1f7662548..9fe7fe019 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -2477,8 +2477,6 @@ class AttributeLinkedSet extends AttributeDefinition $oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay); } - $oFormField->AddValidator(new LinkedSetValidator()); - parent::MakeFormField($oObject, $oFormField); return $oFormField; diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index d74eb09ed..5b700ec56 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -416,13 +416,16 @@ return array( 'Combodo\\iTop\\Form\\Field\\UrlField' => $baseDir . '/sources/Form/Field/UrlField.php', 'Combodo\\iTop\\Form\\Form' => $baseDir . '/sources/Form/Form.php', 'Combodo\\iTop\\Form\\FormManager' => $baseDir . '/sources/Form/FormManager.php', + 'Combodo\\iTop\\Form\\Helper\\FieldHelper' => $baseDir . '/sources/Form/Helper/FieldHelper.php', 'Combodo\\iTop\\Form\\Validator\\AbstractRegexpValidator' => $baseDir . '/sources/Form/Validator/AbstractRegexpValidator.php', 'Combodo\\iTop\\Form\\Validator\\AbstractValidator' => $baseDir . '/sources/Form/Validator/AbstractValidator.php', 'Combodo\\iTop\\Form\\Validator\\CustomRegexpValidator' => $baseDir . '/sources/Form/Validator/CustomRegexpValidator.php', 'Combodo\\iTop\\Form\\Validator\\IntegerValidator' => $baseDir . '/sources/Form/Validator/IntegerValidator.php', 'Combodo\\iTop\\Form\\Validator\\LinkedSetValidator' => $baseDir . '/sources/Form/Validator/LinkedSetValidator.php', 'Combodo\\iTop\\Form\\Validator\\MandatoryValidator' => $baseDir . '/sources/Form/Validator/MandatoryValidator.php', + 'Combodo\\iTop\\Form\\Validator\\MultipleChoicesValidator' => $baseDir . '/sources/Form/Validator/MultipleChoicesValidator.php', 'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => $baseDir . '/sources/Form/Validator/NotEmptyExtKeyValidator.php', + 'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => $baseDir . '/sources/Form/Validator/SelectObjectValidator.php', 'Combodo\\iTop\\Form\\Validator\\Validator' => $baseDir . '/sources/Form/Validator/Validator.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => $baseDir . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php', @@ -534,6 +537,7 @@ return array( 'DesignerSubFormField' => $baseDir . '/application/forms.class.inc.php', 'DesignerTabularForm' => $baseDir . '/application/forms.class.inc.php', 'DesignerTextField' => $baseDir . '/application/forms.class.inc.php', + 'DesignerXMLField' => $baseDir . '/application/forms.class.inc.php', 'Dict' => $baseDir . '/core/dict.class.inc.php', 'DictException' => $baseDir . '/application/exceptions/dict/DictException.php', 'DictExceptionMissingString' => $baseDir . '/application/exceptions/dict/DictExceptionMissingString.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index b41cc4d5a..f764ea377 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -780,13 +780,16 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Form\\Field\\UrlField' => __DIR__ . '/../..' . '/sources/Form/Field/UrlField.php', 'Combodo\\iTop\\Form\\Form' => __DIR__ . '/../..' . '/sources/Form/Form.php', 'Combodo\\iTop\\Form\\FormManager' => __DIR__ . '/../..' . '/sources/Form/FormManager.php', + 'Combodo\\iTop\\Form\\Helper\\FieldHelper' => __DIR__ . '/../..' . '/sources/Form/Helper/FieldHelper.php', 'Combodo\\iTop\\Form\\Validator\\AbstractRegexpValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/AbstractRegexpValidator.php', 'Combodo\\iTop\\Form\\Validator\\AbstractValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/AbstractValidator.php', 'Combodo\\iTop\\Form\\Validator\\CustomRegexpValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/CustomRegexpValidator.php', 'Combodo\\iTop\\Form\\Validator\\IntegerValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/IntegerValidator.php', 'Combodo\\iTop\\Form\\Validator\\LinkedSetValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/LinkedSetValidator.php', 'Combodo\\iTop\\Form\\Validator\\MandatoryValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/MandatoryValidator.php', + 'Combodo\\iTop\\Form\\Validator\\MultipleChoicesValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/MultipleChoicesValidator.php', 'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/NotEmptyExtKeyValidator.php', + 'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/SelectObjectValidator.php', 'Combodo\\iTop\\Form\\Validator\\Validator' => __DIR__ . '/../..' . '/sources/Form/Validator/Validator.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php', @@ -898,6 +901,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'DesignerSubFormField' => __DIR__ . '/../..' . '/application/forms.class.inc.php', 'DesignerTabularForm' => __DIR__ . '/../..' . '/application/forms.class.inc.php', 'DesignerTextField' => __DIR__ . '/../..' . '/application/forms.class.inc.php', + 'DesignerXMLField' => __DIR__ . '/../..' . '/application/forms.class.inc.php', 'Dict' => __DIR__ . '/../..' . '/core/dict.class.inc.php', 'DictException' => __DIR__ . '/../..' . '/application/exceptions/dict/DictException.php', 'DictExceptionMissingString' => __DIR__ . '/../..' . '/application/exceptions/dict/DictExceptionMissingString.php', diff --git a/sources/Form/Field/AbstractSimpleField.php b/sources/Form/Field/AbstractSimpleField.php index 65777bae3..451ce5cf7 100644 --- a/sources/Form/Field/AbstractSimpleField.php +++ b/sources/Form/Field/AbstractSimpleField.php @@ -26,12 +26,13 @@ class AbstractSimpleField extends Field if (!$bEmpty || $this->GetMandatory()) { foreach ($this->GetValidators() as $oValidator) { - [$bIsFieldValid, $sValidationErrorMessage] = $oValidator->Validate($this->GetCurrentValue()); + $aValidationErrorMessages = $oValidator->Validate($this->GetCurrentValue()); - /** @var bool $bIsFieldValid */ - if (false === $bIsFieldValid) { + if (count($aValidationErrorMessages) > 0) { $this->SetValid(false); - $this->AddErrorMessage($sValidationErrorMessage); + foreach ($aValidationErrorMessages as $sErrorMessage) { + $this->AddErrorMessage($sErrorMessage); + } } } } diff --git a/sources/Form/Field/Field.php b/sources/Form/Field/Field.php index 8bda28540..c89a077e7 100644 --- a/sources/Form/Field/Field.php +++ b/sources/Form/Field/Field.php @@ -505,15 +505,33 @@ abstract class Field return $this; } + /** + * @param string $sValidatorClassName validator class name, should be one of {@see AbstractValidator} children + * @return $this + * @since 3.1.0 N°6414 + */ + final public function RemoveValidatorsOfClass(string $sValidatorClassName) + { + foreach ($this->aValidators as $iKey => $oValue) { + if ($oValue instanceof $sValidatorClassName) { + unset($this->aValidators[$iKey]); + } + } + + return $this; + } + /** * Note : Function is protected as aErrorMessages should not be add from outside * * @param string $sErrorMessage + * * @return $this */ protected function AddErrorMessage(string $sErrorMessage) { $this->aErrorMessages[] = $sErrorMessage; + return $this; } diff --git a/sources/Form/Field/LinkedSetField.php b/sources/Form/Field/LinkedSetField.php index 14f6cdc9c..1c76dba90 100644 --- a/sources/Form/Field/LinkedSetField.php +++ b/sources/Form/Field/LinkedSetField.php @@ -21,8 +21,7 @@ namespace Combodo\iTop\Form\Field; use Closure; -use Dict; -use ormLinkSet; +use Combodo\iTop\Form\Validator\LinkedSetValidator; /** * Description of LinkedSetField @@ -55,7 +54,7 @@ class LinkedSetField extends AbstractSimpleField protected $aLimitedAccessItemIDs; /** @var array $aAttributesToDisplay */ protected $aAttributesToDisplay; - /** @var array $aLnkAttributesToDisplay */ + /** @var array $aLnkAttributesToDisplay attcode as key */ protected $aLnkAttributesToDisplay; /** @var string $sSearchEndpoint */ protected $sSearchEndpoint; @@ -281,25 +280,25 @@ class LinkedSetField extends AbstractSimpleField public function GetLnkAttributesToDisplay(bool $bAttCodesOnly = false) { return ($bAttCodesOnly) ? array_keys($this->aLnkAttributesToDisplay) : $this->aLnkAttributesToDisplay; - } + } - /** - * - * @since 3.1 - * - * @param array $aAttributesToDisplay - * - * @return $this - */ - public function SetLnkAttributesToDisplay(array $aAttributesToDisplay) - { - $this->aLnkAttributesToDisplay = $aAttributesToDisplay; + /** + * @param array $aAttributesToDisplay + * @return $this + * @since 3.1.0 N°803 + */ + public function SetLnkAttributesToDisplay(array $aAttributesToDisplay) + { + $this->aLnkAttributesToDisplay = $aAttributesToDisplay; - return $this; - } + $this->RemoveValidatorsOfClass(LinkedSetValidator::class); + $this->AddValidator(new LinkedSetValidator($aAttributesToDisplay)); - /** - * @return string|null + return $this; + } + + /** + * @return string|null */ public function GetSearchEndpoint() { @@ -349,39 +348,4 @@ class LinkedSetField extends AbstractSimpleField { return in_array($iItemID, $this->aLimitedAccessItemIDs, false); } - - /** @inheritdoc @since 3.1 */ - public function Validate() - { - $bValid = parent::Validate(); - - /** @var ormLinkSet $oSet */ - $oSet = $this->GetCurrentValue(); - - // retrieve displayed attributes - $aAttributesToDisplayCodes = $this->GetLnkAttributesToDisplay(true); - - // validate each links... - /** @var \DBObject $oItem */ - foreach ($oSet as $oItem) { - $aChanges = $oItem->ListChanges(); - foreach ($aChanges as $sAttCode => $value) { - if (!in_array($sAttCode, $aAttributesToDisplayCodes)) { - continue; - } - $res = $oItem->CheckValue($sAttCode); - if ($res !== true) { - $sAttLabel = $this->GetLabel($sAttCode); - $sItem = $oItem->Get('friendlyname') != '' ? $oItem->Get('friendlyname') : Dict::S('UI:Links:NewItem'); - $sIssue = Dict::Format('Core:CheckValueError', $sAttLabel, $sAttCode, $res); - $this->AddErrorMessage(''.$sItem.' : '.$sIssue); - $bValid = false; - } - } - } - - $oSet->Rewind(); - - return $bValid; - } } diff --git a/sources/Form/Field/MultipleChoicesField.php b/sources/Form/Field/MultipleChoicesField.php index a86587bc1..097c3e278 100644 --- a/sources/Form/Field/MultipleChoicesField.php +++ b/sources/Form/Field/MultipleChoicesField.php @@ -20,8 +20,7 @@ namespace Combodo\iTop\Form\Field; use Closure; -use ContextTag; -use utils; +use Combodo\iTop\Form\Validator\MultipleChoicesValidator; /** * Description of MultipleChoicesField @@ -51,6 +50,8 @@ abstract class MultipleChoicesField extends AbstractSimpleField $this->bMultipleValuesEnabled = static::DEFAULT_MULTIPLE_VALUES_ENABLED; $this->aChoices = array(); $this->currentValue = array(); + + $this->InitValidators(); } /** @@ -179,69 +180,68 @@ abstract class MultipleChoicesField extends AbstractSimpleField public function SetChoices(array $aChoices) { $this->aChoices = $aChoices; + + $this->InitValidators(); + return $this; } /** * @param string $sId - * @param null $choice + * @param null $choice choice value (eg label) * * @return $this */ public function AddChoice(string $sId, $choice = null) { - if ($choice === null) - { + if ($choice === null) { $choice = $sId; } $this->aChoices[$sId] = $choice; - return $this; - } - /** - * @param string $sId - * - * @return $this - */ - public function RemoveChoice(string $sId) - { - if (in_array($sId, $this->aChoices)) - { - unset($this->aChoices[$sId]); - } - return $this; - } + $this->InitValidators(); - public function Validate() { - $this->SetValid(true); - $this->EmptyErrorMessages(); + return $this; + } - if ((ContextTag::Check(ContextTag::TAG_REST)) && ($this->GetReadOnly() === false)) { - // Only doing the check when coming from the REST API, as the user portal might send invalid values (see VerifyCurrentValue() method below) - // Also do not check read only fields, are they are send with a null value when submitting request template from the console - if (count($this->currentValue) > 0) { - foreach ($this->currentValue as $sCode => $value) { - if (utils::IsNullOrEmptyString($value)) { - continue; - } - if (false === array_key_exists($value, $this->aChoices)) { - $this->SetValid(false); - $this->AddErrorMessage("Value ({$value}) is not part of the field possible values list"); - } - } - } - } + /** + * @param string $sId + * + * @return $this + */ + public function RemoveChoice(string $sId) + { + if (in_array($sId, $this->aChoices)) { + unset($this->aChoices[$sId]); + } - foreach ($this->GetValidators() as $oValidator) { - foreach ($this->currentValue as $value) { - if (!preg_match($oValidator->GetRegExp(true), $value)) { - $this->SetValid(false); - $this->AddErrorMessage($oValidator->GetErrorMessage()); - } - } - } + return $this; + } - return $this->GetValid(); - } + /** + * @param bool $bReadOnly + * @return MultipleChoicesField + * @since 3.1.0 N°6414 + */ + public function SetReadOnly(bool $bReadOnly) + { + if ($bReadOnly) { + /** @noinspection PhpRedundantOptionalArgumentInspection */ + $this->SetValidationDisabled(true); + } else { + $this->SetValidationDisabled(false); + } + return parent::SetReadOnly($bReadOnly); + } + + /** + * @return void + * @since 3.1.0 N°6414 + */ + protected function InitValidators(): void + { + $this->RemoveValidatorsOfClass(MultipleChoicesValidator::class); + $this->AddValidator(new MultipleChoicesValidator($this->aChoices)); + } } diff --git a/sources/Form/Field/MultipleSelectField.php b/sources/Form/Field/MultipleSelectField.php index 6e131783b..61199073b 100644 --- a/sources/Form/Field/MultipleSelectField.php +++ b/sources/Form/Field/MultipleSelectField.php @@ -28,5 +28,4 @@ class MultipleSelectField extends SelectField { /** @inheritDoc */ const DEFAULT_MULTIPLE_VALUES_ENABLED = true; - } diff --git a/sources/Form/Field/SelectObjectField.php b/sources/Form/Field/SelectObjectField.php index a2d3a60f4..345f0f8d2 100644 --- a/sources/Form/Field/SelectObjectField.php +++ b/sources/Form/Field/SelectObjectField.php @@ -20,18 +20,14 @@ namespace Combodo\iTop\Form\Field; -use BinaryExpression; use Closure; +use Combodo\iTop\Form\Helper\FieldHelper; use Combodo\iTop\Form\Validator\AbstractValidator; use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator; -use ContextTag; -use DBObjectSet; +use Combodo\iTop\Form\Validator\SelectObjectValidator; use DBSearch; use DeprecatedCallsLog; -use FieldExpression; use MetaModel; -use ScalarExpression; -use utils; /** * Description of SelectObjectField @@ -95,6 +91,9 @@ class SelectObjectField extends AbstractSimpleField { $this->oSearch = $oSearch; + $this->RemoveValidatorsOfClass(SelectObjectValidator::class); + $this->AddValidator(new SelectObjectValidator($oSearch)); + return $this; } @@ -229,27 +228,6 @@ class SelectObjectField extends AbstractSimpleField return $this->sSearchEndpoint; } - public function Validate() { - if ((ContextTag::Check(ContextTag::TAG_REST)) && ($this->GetReadOnly() === false)) { - // Only doing the check when coming from the REST API, as the user portal might send invalid values (see VerifyCurrentValue() method below) - // Also do not check read only fields, are they are send with a null value when submitting request template from the console - $sCurrentValueForExtKey = $this->currentValue; - if (utils::IsNotNullOrEmptyString($sCurrentValueForExtKey) && ($sCurrentValueForExtKey !== 0)) { - $oSetForExistingCurrentValue = $this->GetObjectsSet(); - $iObjectsCount = $oSetForExistingCurrentValue->CountWithLimit(1); - - if ($iObjectsCount === 0) { - $this->SetValid(false); - $this->AddErrorMessage("Value $sCurrentValueForExtKey does not match the corresponding filter set"); - - return $this->GetValid(); - } - } - } - - return parent::Validate(); - } - /** * Resets current value if not among allowed ones. * By default, reset is done ONLY when the field is not read-only. @@ -283,25 +261,11 @@ class SelectObjectField extends AbstractSimpleField */ public function ResetCurrentValueIfNotAmongAllowedValues(bool $bAlways = false) { if (!$this->GetReadOnly() || $bAlways) { - $oValuesSet = $this->GetObjectsSet(); + $oValuesSet = FieldHelper::GetObjectsSetFromSearchAndCurrentValueId($this->oSearch, $this->currentValue); if ($oValuesSet->Count() === 0) { $this->currentValue = null; } } } - - final protected function GetObjectsSet() { - $sCurrentValueForExtKey = $this->currentValue; - - $oSearchForExistingCurrentValue = $this->oSearch->DeepClone(); - $oCheckIdAgainstCurrentValueExpression = new BinaryExpression( - new FieldExpression('id', $oSearchForExistingCurrentValue->GetClassAlias()), - '=', - new ScalarExpression($sCurrentValueForExtKey) - ); - $oSearchForExistingCurrentValue->AddConditionExpression($oCheckIdAgainstCurrentValueExpression); - - return new DBObjectSet($oSearchForExistingCurrentValue); - } } diff --git a/sources/Form/Helper/FieldHelper.php b/sources/Form/Helper/FieldHelper.php new file mode 100644 index 000000000..66d11df2a --- /dev/null +++ b/sources/Form/Helper/FieldHelper.php @@ -0,0 +1,36 @@ +DeepClone(); + $oCheckIdAgainstCurrentValueExpression = new BinaryExpression( + new FieldExpression('id', $oSearchForExistingCurrentValue->GetClassAlias()), + '=', + new ScalarExpression($sCurrentValueId) + ); + $oSearchForExistingCurrentValue->AddConditionExpression($oCheckIdAgainstCurrentValueExpression); + + return new DBObjectSet($oSearchForExistingCurrentValue); + } + +} \ No newline at end of file diff --git a/sources/Form/Validator/AbstractRegexpValidator.php b/sources/Form/Validator/AbstractRegexpValidator.php index a3434f54d..d5afa7860 100644 --- a/sources/Form/Validator/AbstractRegexpValidator.php +++ b/sources/Form/Validator/AbstractRegexpValidator.php @@ -18,23 +18,23 @@ abstract class AbstractRegexpValidator extends AbstractValidator protected string $sRegExp; - public function __construct(?string $sErrorMessage = null) - { - $this->sRegExp = static::DEFAULT_REGEXP; - parent::__construct($sErrorMessage); - } + public function __construct(?string $sErrorMessage = null) + { + $this->sRegExp = static::DEFAULT_REGEXP; + parent::__construct($sErrorMessage); + } - public function Validate($value): array - { - if (is_null($value)) { - $value = ''; // calling preg_match with null as subject is deprecated since PHP 8.1 - } - if (preg_match($this->GetRegExp(true), $value)) { - return [true, null]; - } + public function Validate($value): array + { + if (is_null($value)) { + $value = ''; // calling preg_match with null as subject is deprecated since PHP 8.1 + } + if (preg_match($this->GetRegExp(true), $value)) { + return []; + } - return [false, $this->sErrorMessage]; - } + return [$this->sErrorMessage]; + } /** * Returns the regular expression of the validator. diff --git a/sources/Form/Validator/AbstractValidator.php b/sources/Form/Validator/AbstractValidator.php index da0780e8a..6bfec4928 100644 --- a/sources/Form/Validator/AbstractValidator.php +++ b/sources/Form/Validator/AbstractValidator.php @@ -20,19 +20,20 @@ abstract class AbstractValidator /** @var string message / dict key to use when an error occurs */ protected string $sErrorMessage; - public function __construct(?string $sErrorMessage) + public function __construct(?string $sErrorMessage = null) { if (false === utils::IsNullOrEmptyString($sErrorMessage)) { $this->sErrorMessage = $sErrorMessage; - } else { - $this->sErrorMessage = self::DEFAULT_ERROR_MESSAGE; + } + else { + $this->sErrorMessage = static::DEFAULT_ERROR_MESSAGE; } } /** * @param mixed $value * - * @return array boolean valid for valid / invalid, and error message if invalid + * @return string[] list of error messages, empty array if no error */ abstract public function Validate($value): array; diff --git a/sources/Form/Validator/LinkedSetValidator.php b/sources/Form/Validator/LinkedSetValidator.php index 5ac6fbd2a..dd6d109a7 100644 --- a/sources/Form/Validator/LinkedSetValidator.php +++ b/sources/Form/Validator/LinkedSetValidator.php @@ -19,12 +19,56 @@ namespace Combodo\iTop\Form\Validator; +use Dict; +use ormLinkSet; +use utils; + /** * Description of LinkedSetValidator * - * @since 3.1 + * @since 3.1.0 N°6414 */ class LinkedSetValidator extends AbstractRegexpValidator { - public const VALIDATOR_NAME = 'LinkedSetValidator'; + public const VALIDATOR_NAME = 'linkedset_validator'; + private $aAttributesToDisplayCodes; + + public function __construct($aAttributesToDisplayCodes) + { + $this->aAttributesToDisplayCodes = $aAttributesToDisplayCodes; + + parent::__construct(); + } + + public function Validate($value): array + { + $aErrorMessages = []; + + /** @var ormLinkSet $oSet */ + $oSet = $value; + + // validate each links... + /** @var \DBObject $oItem */ + foreach ($oSet as $oItem) { + $aChanges = $oItem->ListChanges(); + foreach ($aChanges as $sAttCode => $AttValue) { + if (!in_array($sAttCode, $this->aAttributesToDisplayCodes)) { + continue; + } + $res = $oItem->CheckValue($sAttCode); + if ($res !== true) { + $sAttLabel = $oItem->GetLabel($sAttCode); + $sItem = utils::IsNullOrEmptyString($oItem->Get('friendlyname')) + ? Dict::S('UI:Links:NewItem') + : $oItem->Get('friendlyname'); + $sIssue = Dict::Format('Core:CheckValueError', $sAttLabel, $sAttCode, $res); + $aErrorMessages[] = '' . $sItem . ' : ' . $sIssue; + } + } + } + + $oSet->Rewind(); + + return $aErrorMessages; + } } diff --git a/sources/Form/Validator/MultipleChoicesValidator.php b/sources/Form/Validator/MultipleChoicesValidator.php new file mode 100644 index 000000000..408ac6350 --- /dev/null +++ b/sources/Form/Validator/MultipleChoicesValidator.php @@ -0,0 +1,59 @@ +aChoices = $aChoices; + } + + /** + * @param mixed $value Warning can either be an array (if multiple values are allowed in the field) or a primitive : {@see \Combodo\iTop\Form\Field\MultipleChoicesField::GetCurrentValue()} + * + * @return array|string[] + */ + public function Validate($value): array + { + $aErrorMessages = []; + if (false === is_array($value)) { + $this->CheckValueAgainstChoices($value, $aErrorMessages); + + return $aErrorMessages; + } + + if (count($value) === 0) { + return []; + } + + /** @noinspection PhpUnusedLocalVariableInspection */ + foreach ($value as $sCode => $valueItem) { + if (utils::IsNullOrEmptyString($valueItem)) { + continue; + } + $this->CheckValueAgainstChoices($valueItem, $aErrorMessages); + } + + return $aErrorMessages; + } + + private function CheckValueAgainstChoices(string $sValue, array &$aErrorMessages): void + { + if (false === array_key_exists($sValue, $this->aChoices)) { + $aErrorMessages[] = "Value ({$sValue}) is not part of the field possible values list"; + } + } +} \ No newline at end of file diff --git a/sources/Form/Validator/SelectObjectValidator.php b/sources/Form/Validator/SelectObjectValidator.php new file mode 100644 index 000000000..8c56fefda --- /dev/null +++ b/sources/Form/Validator/SelectObjectValidator.php @@ -0,0 +1,45 @@ +oSearch = $oSearch; + } + + public function Validate($value): array + { + if (utils::IsNullOrEmptyString($value)) { + return []; + } + if (($value === 0) || ($value === '0')) { + return []; + } + + $oSetForExistingCurrentValue = FieldHelper::GetObjectsSetFromSearchAndCurrentValueId($this->oSearch, $value); + $iObjectsCount = $oSetForExistingCurrentValue->CountWithLimit(1); + + if ($iObjectsCount === 0) { + return ["Value $value does not match the corresponding filter set"]; + } + + return []; + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/sources/Form/Field/FieldTest.php b/tests/php-unit-tests/unitary-tests/sources/Form/Field/FieldTest.php index 7bc1cd1f5..d915082ef 100644 --- a/tests/php-unit-tests/unitary-tests/sources/Form/Field/FieldTest.php +++ b/tests/php-unit-tests/unitary-tests/sources/Form/Field/FieldTest.php @@ -100,4 +100,28 @@ class FieldTest extends ItopTestCase $this->assertTrue($bIsSubFormFieldValidAfterFieldUpdate); $this->assertCount(0, $oSubFormField->GetErrorMessages()); } + + public function testRemoveValidatorsOfClass(): void { + $oField = new StringField('test'); + + $this->assertCount(0, $oField->GetValidators()); + $oField->RemoveValidatorsOfClass(CustomRegexpValidator::class); + $this->assertCount(0, $oField->GetValidators()); + + $oField->AddValidator(new IntegerValidator()); + $this->assertCount(1, $oField->GetValidators()); + $oField->RemoveValidatorsOfClass(CustomRegexpValidator::class); + $this->assertCount(1, $oField->GetValidators()); + + $oField->AddValidator(new CustomRegexpValidator('^.*$')); + $this->assertCount(2, $oField->GetValidators()); + $oField->RemoveValidatorsOfClass(CustomRegexpValidator::class); + $this->assertCount(1, $oField->GetValidators()); + + $oField->AddValidator(new CustomRegexpValidator('^.*$')); + $oField->AddValidator(new CustomRegexpValidator('^.*$')); + $this->assertCount(3, $oField->GetValidators()); + $oField->RemoveValidatorsOfClass(CustomRegexpValidator::class); + $this->assertCount(1, $oField->GetValidators()); + } } \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/sources/Form/LinkedSetFieldTest.php b/tests/php-unit-tests/unitary-tests/sources/Form/LinkedSetFieldTest.php new file mode 100644 index 000000000..6d006257e --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/sources/Form/LinkedSetFieldTest.php @@ -0,0 +1,40 @@ +SetIndirect(false); + $oLinkedSetField->SetTargetClass($sLinkedClass); + $oLinkedSetField->SetLinkedClass($sLinkedClass); + $oLinkedSetField->SetLnkAttributesToDisplay(['title' => 'title']); + + $oSetThreeExistingTickets = new ormLinkSet(Person::class, 'tickets_list'); + $this->CreateTestOrganization(); + $oUserRequest1 = $this->CreateUserRequest(1); + $oUserRequest2 = $this->CreateUserRequest(2); + $oUserRequest3 = $this->CreateUserRequest(3); + $oSetThreeExistingTickets->AddItem($oUserRequest1); + $oSetThreeExistingTickets->AddItem($oUserRequest2); + $oSetThreeExistingTickets->AddItem($oUserRequest3); + $oLinkedSetField->SetCurrentValue($oSetThreeExistingTickets); + $this->assertTrue($oLinkedSetField->Validate(), 'A set with existing objects and no modifications must be OK'); + + $oUserRequest1->Set('title', 'this a modified title !'); + $this->assertTrue($oLinkedSetField->Validate(), 'A set with existing objects and a valid modification must be OK'); + + $oUserRequest1->Set('title', ''); + $this->assertFalse($oLinkedSetField->Validate(), 'A set with existing objects and an invalid modification must be KO'); + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/sources/Form/MultipleChoicesFieldTest.php b/tests/php-unit-tests/unitary-tests/sources/Form/MultipleChoicesFieldTest.php new file mode 100644 index 000000000..8724502e2 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/sources/Form/MultipleChoicesFieldTest.php @@ -0,0 +1,107 @@ +AddChoice('A'); + $oMultipleChoicesField->AddChoice('B'); + $oMultipleChoicesField->AddChoice('C'); + $oMultipleChoicesField->AddChoice('D'); + + // N°1150 the control was added for the REST API initially and was only triggered with the corresponding ContextTag + $oRestContext = new ContextTag(ContextTag::TAG_REST); + $this->ValidateMultipleSelectField($oMultipleChoicesField); + + // retrying without REST context + unset($oRestContext); + $this->ValidateMultipleSelectField($oMultipleChoicesField); + } + + private function ValidateMultipleSelectField(MultipleChoicesField $oMultipleChoicesField): void + { + $oMultipleChoicesField->SetCurrentValue(null); + $this->assertTrue($oMultipleChoicesField->Validate(), 'No value must be valid'); + + $sExistingValue = 'A'; + $sNonExistingValue = 'Non existing choice'; + $sNonExistingValue1 = 'Non existing choice 1'; + $sNonExistingValue2 = 'Non existing choice 2'; + + $oMultipleChoicesField->SetCurrentValue($sExistingValue); + $this->assertTrue($oMultipleChoicesField->Validate(), 'Value among possible ones is valid'); + + $oMultipleChoicesField->SetCurrentValue($sNonExistingValue); + $this->assertFalse($oMultipleChoicesField->Validate(), 'Value not among possible ones is invalid'); + $this->assertCount(1, $oMultipleChoicesField->GetErrorMessages()); + $this->assertStringContainsString($sNonExistingValue, $oMultipleChoicesField->GetErrorMessages()[0]); + + $oMultipleChoicesField->SetCurrentValue([$sNonExistingValue1, $sNonExistingValue2]); + $this->assertFalse($oMultipleChoicesField->Validate(), 'Multiple values not among possible ones is invalid'); + $this->assertCount(2, $oMultipleChoicesField->GetErrorMessages()); + $this->assertStringContainsString($sNonExistingValue1, $oMultipleChoicesField->GetErrorMessages()[0]); + $this->assertStringContainsString($sNonExistingValue2, $oMultipleChoicesField->GetErrorMessages()[1]); + + $oMultipleChoicesField->SetCurrentValue([$sExistingValue, $sNonExistingValue]); + $this->assertFalse($oMultipleChoicesField->Validate(), 'Valid value + Value not among possible ones is invalid'); + $this->assertCount(1, $oMultipleChoicesField->GetErrorMessages()); + $this->assertStringContainsString($sNonExistingValue, $oMultipleChoicesField->GetErrorMessages()[0]); + + $oMultipleChoicesField->SetCurrentValue([$sExistingValue, $sNonExistingValue1, $sNonExistingValue2]); + $this->assertFalse($oMultipleChoicesField->Validate(), 'Valid value + Multiple values not among possible ones is invalid'); + $this->assertCount(2, $oMultipleChoicesField->GetErrorMessages()); + $this->assertStringContainsString($sNonExistingValue1, $oMultipleChoicesField->GetErrorMessages()[0]); + $this->assertStringContainsString($sNonExistingValue2, $oMultipleChoicesField->GetErrorMessages()[1]); + } + + public function testValidateForSelectField(): void + { + $oMultipleChoicesField = new SelectField('test'); + $oMultipleChoicesField->AddChoice('A'); + $oMultipleChoicesField->AddChoice('B'); + $oMultipleChoicesField->AddChoice('C'); + $oMultipleChoicesField->AddChoice('D'); + + // N°1150 the control was added for the REST API initially and was only triggered with the corresponding ContextTag + $oRestContext = new ContextTag(ContextTag::TAG_REST); + $this->ValidateSelectField($oMultipleChoicesField); + + // retrying without REST context + unset($oRestContext); + $this->ValidateSelectField($oMultipleChoicesField); + + $oMultipleChoicesField = new SelectField('test'); + $oMultipleChoicesField->SetChoices(['A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D']); + $this->ValidateSelectField($oMultipleChoicesField); + } + + private function ValidateSelectField(MultipleChoicesField $oMultipleChoicesField): void + { + $oMultipleChoicesField->SetCurrentValue(null); + $this->assertTrue($oMultipleChoicesField->Validate(), 'No value must be valid'); + + $sExistingValue = 'A'; + $sNonExistingValue = 'Non existing choice'; + + $oMultipleChoicesField->SetCurrentValue($sExistingValue); + $this->assertTrue($oMultipleChoicesField->Validate(), 'Value among possible ones is valid'); + + $oMultipleChoicesField->SetCurrentValue($sNonExistingValue); + $this->assertFalse($oMultipleChoicesField->Validate(), 'Value not among possible ones is invalid'); + $this->assertCount(1, $oMultipleChoicesField->GetErrorMessages()); + $this->assertStringContainsString($sNonExistingValue, $oMultipleChoicesField->GetErrorMessages()[0]); + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/sources/Form/SelectObjectFieldTest.php b/tests/php-unit-tests/unitary-tests/sources/Form/SelectObjectFieldTest.php new file mode 100644 index 000000000..1f788f043 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/sources/Form/SelectObjectFieldTest.php @@ -0,0 +1,43 @@ +SetSearch(DBObjectSearch::FromOQL('SELECT '.Organization::class)); + + // N°1150 the control was added for the REST API initially and was only triggered with the corresponding ContextTag + $oRestContext = new ContextTag(ContextTag::TAG_REST); + $this->ValidateSelectObjectField($oSelectObjectField); + + // retrying without REST context + unset($oRestContext); + $this->ValidateSelectObjectField($oSelectObjectField); + } + + private function ValidateSelectObjectField(SelectObjectField $oSelectObjectField): void { + $oSelectObjectField->SetCurrentValue(null); + $this->assertTrue($oSelectObjectField->Validate(), 'No value must be valid'); + + $sExistingOrganizationId = 1; + $oSelectObjectField->SetCurrentValue($sExistingOrganizationId); + $this->assertTrue($oSelectObjectField->Validate(), 'An existing object id must be valid'); + + $sNonExistingOrganizationId = 999999; + $oSelectObjectField->SetCurrentValue($sNonExistingOrganizationId); + $this->assertFalse($oSelectObjectField->Validate(), 'An non existing object id must be invalid'); + $this->assertCount(1, $oSelectObjectField->GetErrorMessages()); + $this->assertStringContainsString($sNonExistingOrganizationId, $oSelectObjectField->GetErrorMessages()[0]); + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/sources/Form/Validator/ValidatorTest.php b/tests/php-unit-tests/unitary-tests/sources/Form/Validator/ValidatorTest.php index c7c066f38..2f87eacc1 100644 --- a/tests/php-unit-tests/unitary-tests/sources/Form/Validator/ValidatorTest.php +++ b/tests/php-unit-tests/unitary-tests/sources/Form/Validator/ValidatorTest.php @@ -7,6 +7,7 @@ namespace Combodo\iTop\Test\UnitTest\Sources\Form\Validator; use Combodo\iTop\Form\Field\StringField; +use Combodo\iTop\Form\Validator\MandatoryValidator; use Combodo\iTop\Test\UnitTest\ItopTestCase; class ValidatorTest extends ItopTestCase @@ -24,5 +25,7 @@ class ValidatorTest extends ItopTestCase $bIsMandatoryFieldValidWithNoValue = $oField->Validate(); $this->assertFalse($bIsMandatoryFieldValidWithNoValue); $this->assertNotEmpty($oField->GetErrorMessages()); + $this->assertCount(1, $oField->GetErrorMessages()); + $this->assertStringContainsString(MandatoryValidator::DEFAULT_ERROR_MESSAGE, $oField->GetErrorMessages()[0]); } } \ No newline at end of file