N°6414 Move existing AbstractSimpleField::Validate impl to custom validators

- LinkedSetField
- SelectObjectField
- MultipleChoicesField (warning this hierarchy contains non multiple value fields like SelectField !)

Also change AbstractValidator::Validate signature : now we are returning an array of error messages, so that we can return multiple ones
This commit is contained in:
Pierre Goiffon
2023-06-28 17:56:04 +02:00
parent d085f15b6d
commit 6cc2d49cd5
20 changed files with 526 additions and 172 deletions

View File

@@ -2477,8 +2477,6 @@ class AttributeLinkedSet extends AttributeDefinition
$oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay); $oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay);
} }
$oFormField->AddValidator(new LinkedSetValidator());
parent::MakeFormField($oObject, $oFormField); parent::MakeFormField($oObject, $oFormField);
return $oFormField; return $oFormField;

View File

@@ -416,13 +416,16 @@ return array(
'Combodo\\iTop\\Form\\Field\\UrlField' => $baseDir . '/sources/Form/Field/UrlField.php', 'Combodo\\iTop\\Form\\Field\\UrlField' => $baseDir . '/sources/Form/Field/UrlField.php',
'Combodo\\iTop\\Form\\Form' => $baseDir . '/sources/Form/Form.php', 'Combodo\\iTop\\Form\\Form' => $baseDir . '/sources/Form/Form.php',
'Combodo\\iTop\\Form\\FormManager' => $baseDir . '/sources/Form/FormManager.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\\AbstractRegexpValidator' => $baseDir . '/sources/Form/Validator/AbstractRegexpValidator.php',
'Combodo\\iTop\\Form\\Validator\\AbstractValidator' => $baseDir . '/sources/Form/Validator/AbstractValidator.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\\CustomRegexpValidator' => $baseDir . '/sources/Form/Validator/CustomRegexpValidator.php',
'Combodo\\iTop\\Form\\Validator\\IntegerValidator' => $baseDir . '/sources/Form/Validator/IntegerValidator.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\\LinkedSetValidator' => $baseDir . '/sources/Form/Validator/LinkedSetValidator.php',
'Combodo\\iTop\\Form\\Validator\\MandatoryValidator' => $baseDir . '/sources/Form/Validator/MandatoryValidator.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\\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\\Form\\Validator\\Validator' => $baseDir . '/sources/Form/Validator/Validator.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => $baseDir . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.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', 'DesignerSubFormField' => $baseDir . '/application/forms.class.inc.php',
'DesignerTabularForm' => $baseDir . '/application/forms.class.inc.php', 'DesignerTabularForm' => $baseDir . '/application/forms.class.inc.php',
'DesignerTextField' => $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', 'Dict' => $baseDir . '/core/dict.class.inc.php',
'DictException' => $baseDir . '/application/exceptions/dict/DictException.php', 'DictException' => $baseDir . '/application/exceptions/dict/DictException.php',
'DictExceptionMissingString' => $baseDir . '/application/exceptions/dict/DictExceptionMissingString.php', 'DictExceptionMissingString' => $baseDir . '/application/exceptions/dict/DictExceptionMissingString.php',

View File

@@ -780,13 +780,16 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Form\\Field\\UrlField' => __DIR__ . '/../..' . '/sources/Form/Field/UrlField.php', 'Combodo\\iTop\\Form\\Field\\UrlField' => __DIR__ . '/../..' . '/sources/Form/Field/UrlField.php',
'Combodo\\iTop\\Form\\Form' => __DIR__ . '/../..' . '/sources/Form/Form.php', 'Combodo\\iTop\\Form\\Form' => __DIR__ . '/../..' . '/sources/Form/Form.php',
'Combodo\\iTop\\Form\\FormManager' => __DIR__ . '/../..' . '/sources/Form/FormManager.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\\AbstractRegexpValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/AbstractRegexpValidator.php',
'Combodo\\iTop\\Form\\Validator\\AbstractValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/AbstractValidator.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\\CustomRegexpValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/CustomRegexpValidator.php',
'Combodo\\iTop\\Form\\Validator\\IntegerValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/IntegerValidator.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\\LinkedSetValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/LinkedSetValidator.php',
'Combodo\\iTop\\Form\\Validator\\MandatoryValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/MandatoryValidator.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\\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\\Form\\Validator\\Validator' => __DIR__ . '/../..' . '/sources/Form/Validator/Validator.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.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', 'DesignerSubFormField' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerTabularForm' => __DIR__ . '/../..' . '/application/forms.class.inc.php', 'DesignerTabularForm' => __DIR__ . '/../..' . '/application/forms.class.inc.php',
'DesignerTextField' => __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', 'Dict' => __DIR__ . '/../..' . '/core/dict.class.inc.php',
'DictException' => __DIR__ . '/../..' . '/application/exceptions/dict/DictException.php', 'DictException' => __DIR__ . '/../..' . '/application/exceptions/dict/DictException.php',
'DictExceptionMissingString' => __DIR__ . '/../..' . '/application/exceptions/dict/DictExceptionMissingString.php', 'DictExceptionMissingString' => __DIR__ . '/../..' . '/application/exceptions/dict/DictExceptionMissingString.php',

View File

@@ -26,12 +26,13 @@ class AbstractSimpleField extends Field
if (!$bEmpty || $this->GetMandatory()) { if (!$bEmpty || $this->GetMandatory()) {
foreach ($this->GetValidators() as $oValidator) { foreach ($this->GetValidators() as $oValidator) {
[$bIsFieldValid, $sValidationErrorMessage] = $oValidator->Validate($this->GetCurrentValue()); $aValidationErrorMessages = $oValidator->Validate($this->GetCurrentValue());
/** @var bool $bIsFieldValid */ if (count($aValidationErrorMessages) > 0) {
if (false === $bIsFieldValid) {
$this->SetValid(false); $this->SetValid(false);
$this->AddErrorMessage($sValidationErrorMessage); foreach ($aValidationErrorMessages as $sErrorMessage) {
$this->AddErrorMessage($sErrorMessage);
}
} }
} }
} }

View File

@@ -505,15 +505,33 @@ abstract class Field
return $this; 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 * Note : Function is protected as aErrorMessages should not be add from outside
* *
* @param string $sErrorMessage * @param string $sErrorMessage
*
* @return $this * @return $this
*/ */
protected function AddErrorMessage(string $sErrorMessage) protected function AddErrorMessage(string $sErrorMessage)
{ {
$this->aErrorMessages[] = $sErrorMessage; $this->aErrorMessages[] = $sErrorMessage;
return $this; return $this;
} }

View File

@@ -21,8 +21,7 @@
namespace Combodo\iTop\Form\Field; namespace Combodo\iTop\Form\Field;
use Closure; use Closure;
use Dict; use Combodo\iTop\Form\Validator\LinkedSetValidator;
use ormLinkSet;
/** /**
* Description of LinkedSetField * Description of LinkedSetField
@@ -55,7 +54,7 @@ class LinkedSetField extends AbstractSimpleField
protected $aLimitedAccessItemIDs; protected $aLimitedAccessItemIDs;
/** @var array $aAttributesToDisplay */ /** @var array $aAttributesToDisplay */
protected $aAttributesToDisplay; protected $aAttributesToDisplay;
/** @var array $aLnkAttributesToDisplay */ /** @var array $aLnkAttributesToDisplay attcode as key */
protected $aLnkAttributesToDisplay; protected $aLnkAttributesToDisplay;
/** @var string $sSearchEndpoint */ /** @var string $sSearchEndpoint */
protected $sSearchEndpoint; protected $sSearchEndpoint;
@@ -281,25 +280,25 @@ class LinkedSetField extends AbstractSimpleField
public function GetLnkAttributesToDisplay(bool $bAttCodesOnly = false) public function GetLnkAttributesToDisplay(bool $bAttCodesOnly = false)
{ {
return ($bAttCodesOnly) ? array_keys($this->aLnkAttributesToDisplay) : $this->aLnkAttributesToDisplay; return ($bAttCodesOnly) ? array_keys($this->aLnkAttributesToDisplay) : $this->aLnkAttributesToDisplay;
} }
/** /**
* * @param array $aAttributesToDisplay
* @since 3.1 * @return $this
* * @since 3.1.0 N°803
* @param array $aAttributesToDisplay */
* public function SetLnkAttributesToDisplay(array $aAttributesToDisplay)
* @return $this {
*/ $this->aLnkAttributesToDisplay = $aAttributesToDisplay;
public function SetLnkAttributesToDisplay(array $aAttributesToDisplay)
{
$this->aLnkAttributesToDisplay = $aAttributesToDisplay;
return $this; $this->RemoveValidatorsOfClass(LinkedSetValidator::class);
} $this->AddValidator(new LinkedSetValidator($aAttributesToDisplay));
/** return $this;
* @return string|null }
/**
* @return string|null
*/ */
public function GetSearchEndpoint() public function GetSearchEndpoint()
{ {
@@ -349,39 +348,4 @@ class LinkedSetField extends AbstractSimpleField
{ {
return in_array($iItemID, $this->aLimitedAccessItemIDs, false); 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('<b>'.$sItem.' : </b>'.$sIssue);
$bValid = false;
}
}
}
$oSet->Rewind();
return $bValid;
}
} }

View File

@@ -20,8 +20,7 @@
namespace Combodo\iTop\Form\Field; namespace Combodo\iTop\Form\Field;
use Closure; use Closure;
use ContextTag; use Combodo\iTop\Form\Validator\MultipleChoicesValidator;
use utils;
/** /**
* Description of MultipleChoicesField * Description of MultipleChoicesField
@@ -51,6 +50,8 @@ abstract class MultipleChoicesField extends AbstractSimpleField
$this->bMultipleValuesEnabled = static::DEFAULT_MULTIPLE_VALUES_ENABLED; $this->bMultipleValuesEnabled = static::DEFAULT_MULTIPLE_VALUES_ENABLED;
$this->aChoices = array(); $this->aChoices = array();
$this->currentValue = array(); $this->currentValue = array();
$this->InitValidators();
} }
/** /**
@@ -179,69 +180,68 @@ abstract class MultipleChoicesField extends AbstractSimpleField
public function SetChoices(array $aChoices) public function SetChoices(array $aChoices)
{ {
$this->aChoices = $aChoices; $this->aChoices = $aChoices;
$this->InitValidators();
return $this; return $this;
} }
/** /**
* @param string $sId * @param string $sId
* @param null $choice * @param null $choice choice value (eg label)
* *
* @return $this * @return $this
*/ */
public function AddChoice(string $sId, $choice = null) public function AddChoice(string $sId, $choice = null)
{ {
if ($choice === null) if ($choice === null) {
{
$choice = $sId; $choice = $sId;
} }
$this->aChoices[$sId] = $choice; $this->aChoices[$sId] = $choice;
return $this;
}
/** $this->InitValidators();
* @param string $sId
*
* @return $this
*/
public function RemoveChoice(string $sId)
{
if (in_array($sId, $this->aChoices))
{
unset($this->aChoices[$sId]);
}
return $this;
}
public function Validate() { return $this;
$this->SetValid(true); }
$this->EmptyErrorMessages();
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) * @param string $sId
// 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) { * @return $this
foreach ($this->currentValue as $sCode => $value) { */
if (utils::IsNullOrEmptyString($value)) { public function RemoveChoice(string $sId)
continue; {
} if (in_array($sId, $this->aChoices)) {
if (false === array_key_exists($value, $this->aChoices)) { unset($this->aChoices[$sId]);
$this->SetValid(false); }
$this->AddErrorMessage("Value ({$value}) is not part of the field possible values list");
}
}
}
}
foreach ($this->GetValidators() as $oValidator) { return $this;
foreach ($this->currentValue as $value) { }
if (!preg_match($oValidator->GetRegExp(true), $value)) {
$this->SetValid(false);
$this->AddErrorMessage($oValidator->GetErrorMessage());
}
}
}
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));
}
} }

View File

@@ -28,5 +28,4 @@ class MultipleSelectField extends SelectField
{ {
/** @inheritDoc */ /** @inheritDoc */
const DEFAULT_MULTIPLE_VALUES_ENABLED = true; const DEFAULT_MULTIPLE_VALUES_ENABLED = true;
} }

View File

@@ -20,18 +20,14 @@
namespace Combodo\iTop\Form\Field; namespace Combodo\iTop\Form\Field;
use BinaryExpression;
use Closure; use Closure;
use Combodo\iTop\Form\Helper\FieldHelper;
use Combodo\iTop\Form\Validator\AbstractValidator; use Combodo\iTop\Form\Validator\AbstractValidator;
use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator; use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
use ContextTag; use Combodo\iTop\Form\Validator\SelectObjectValidator;
use DBObjectSet;
use DBSearch; use DBSearch;
use DeprecatedCallsLog; use DeprecatedCallsLog;
use FieldExpression;
use MetaModel; use MetaModel;
use ScalarExpression;
use utils;
/** /**
* Description of SelectObjectField * Description of SelectObjectField
@@ -95,6 +91,9 @@ class SelectObjectField extends AbstractSimpleField
{ {
$this->oSearch = $oSearch; $this->oSearch = $oSearch;
$this->RemoveValidatorsOfClass(SelectObjectValidator::class);
$this->AddValidator(new SelectObjectValidator($oSearch));
return $this; return $this;
} }
@@ -229,27 +228,6 @@ class SelectObjectField extends AbstractSimpleField
return $this->sSearchEndpoint; 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. * Resets current value if not among allowed ones.
* By default, reset is done ONLY when the field is not read-only. * 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) { public function ResetCurrentValueIfNotAmongAllowedValues(bool $bAlways = false) {
if (!$this->GetReadOnly() || $bAlways) { if (!$this->GetReadOnly() || $bAlways) {
$oValuesSet = $this->GetObjectsSet(); $oValuesSet = FieldHelper::GetObjectsSetFromSearchAndCurrentValueId($this->oSearch, $this->currentValue);
if ($oValuesSet->Count() === 0) { if ($oValuesSet->Count() === 0) {
$this->currentValue = null; $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);
}
} }

View File

@@ -0,0 +1,36 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Form\Helper;
use BinaryExpression;
use DBObjectSet;
use DBSearch;
use FieldExpression;
use ScalarExpression;
/**
* Utility methods for {@see \Combodo\iTop\Form\Field\Field} classes
*
* @since 3.1.0 N°6414
*/
class FieldHelper {
/**
* @since 3.1.0 N°6414 Method creation to factorize between uses in {@see \Combodo\iTop\Form\Field\Field} and {@see \Combodo\iTop\Form\Validator\SelectObjectValidator}
*/
public static function GetObjectsSetFromSearchAndCurrentValueId(DBSearch $oSearch, string $sCurrentValueId) {
$oSearchForExistingCurrentValue = $oSearch->DeepClone();
$oCheckIdAgainstCurrentValueExpression = new BinaryExpression(
new FieldExpression('id', $oSearchForExistingCurrentValue->GetClassAlias()),
'=',
new ScalarExpression($sCurrentValueId)
);
$oSearchForExistingCurrentValue->AddConditionExpression($oCheckIdAgainstCurrentValueExpression);
return new DBObjectSet($oSearchForExistingCurrentValue);
}
}

View File

@@ -18,23 +18,23 @@ abstract class AbstractRegexpValidator extends AbstractValidator
protected string $sRegExp; protected string $sRegExp;
public function __construct(?string $sErrorMessage = null) public function __construct(?string $sErrorMessage = null)
{ {
$this->sRegExp = static::DEFAULT_REGEXP; $this->sRegExp = static::DEFAULT_REGEXP;
parent::__construct($sErrorMessage); parent::__construct($sErrorMessage);
} }
public function Validate($value): array public function Validate($value): array
{ {
if (is_null($value)) { if (is_null($value)) {
$value = ''; // calling preg_match with null as subject is deprecated since PHP 8.1 $value = ''; // calling preg_match with null as subject is deprecated since PHP 8.1
} }
if (preg_match($this->GetRegExp(true), $value)) { if (preg_match($this->GetRegExp(true), $value)) {
return [true, null]; return [];
} }
return [false, $this->sErrorMessage]; return [$this->sErrorMessage];
} }
/** /**
* Returns the regular expression of the validator. * Returns the regular expression of the validator.

View File

@@ -20,19 +20,20 @@ abstract class AbstractValidator
/** @var string message / dict key to use when an error occurs */ /** @var string message / dict key to use when an error occurs */
protected string $sErrorMessage; protected string $sErrorMessage;
public function __construct(?string $sErrorMessage) public function __construct(?string $sErrorMessage = null)
{ {
if (false === utils::IsNullOrEmptyString($sErrorMessage)) { if (false === utils::IsNullOrEmptyString($sErrorMessage)) {
$this->sErrorMessage = $sErrorMessage; $this->sErrorMessage = $sErrorMessage;
} else { }
$this->sErrorMessage = self::DEFAULT_ERROR_MESSAGE; else {
$this->sErrorMessage = static::DEFAULT_ERROR_MESSAGE;
} }
} }
/** /**
* @param mixed $value * @param mixed $value
* *
* @return array<bool,?string> 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; abstract public function Validate($value): array;

View File

@@ -19,12 +19,56 @@
namespace Combodo\iTop\Form\Validator; namespace Combodo\iTop\Form\Validator;
use Dict;
use ormLinkSet;
use utils;
/** /**
* Description of LinkedSetValidator * Description of LinkedSetValidator
* *
* @since 3.1 * @since 3.1.0 N°6414
*/ */
class LinkedSetValidator extends AbstractRegexpValidator 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[] = '<b>' . $sItem . ' : </b>' . $sIssue;
}
}
}
$oSet->Rewind();
return $aErrorMessages;
}
} }

View File

@@ -0,0 +1,59 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Form\Validator;
use utils;
class MultipleChoicesValidator extends AbstractValidator
{
public const VALIDATOR_NAME = 'multiple_choices_validator';
/** @var array List of possible choices */
private array $aChoices;
public function __construct(array $aChoices)
{
parent::__construct();
$this->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";
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Form\Validator;
use Combodo\iTop\Form\Helper\FieldHelper;
use DBSearch;
use utils;
class SelectObjectValidator extends AbstractValidator
{
public const VALIDATOR_NAME = 'select_object_validator';
/** @var \DBSearch $oSearch */
private $oSearch;
public function __construct(DBSearch $oSearch)
{
parent::__construct();
$this->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 [];
}
}

View File

@@ -100,4 +100,28 @@ class FieldTest extends ItopTestCase
$this->assertTrue($bIsSubFormFieldValidAfterFieldUpdate); $this->assertTrue($bIsSubFormFieldValidAfterFieldUpdate);
$this->assertCount(0, $oSubFormField->GetErrorMessages()); $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());
}
} }

View File

@@ -0,0 +1,40 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Form;
use Combodo\iTop\Form\Field\LinkedSetField;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ormLinkSet;
use Person;
class LinkedSetFieldTest extends ItopDataTestCase {
public function testValidate(): void {
$sLinkedClass = Ticket::class;
$oLinkedSetField = new LinkedSetField('test');
$oLinkedSetField->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');
}
}

View File

@@ -0,0 +1,107 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Form;
use Combodo\iTop\Form\Field\MultipleChoicesField;
use Combodo\iTop\Form\Field\MultipleSelectField;
use Combodo\iTop\Form\Field\SelectField;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use ContextTag;
class MultipleChoicesFieldTest extends ItopTestCase
{
public function testValidateForMultipleSelectField(): void
{
$oMultipleChoicesField = new MultipleSelectField('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->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]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Form;
use Combodo\iTop\Form\Field\SelectObjectField;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ContextTag;
use DBObjectSearch;
use Organization;
class SelectObjectFieldTest extends ItopDataTestCase {
public function testValidate(): void {
$oSelectObjectField = new SelectObjectField('test');
$oSelectObjectField->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]);
}
}

View File

@@ -7,6 +7,7 @@
namespace Combodo\iTop\Test\UnitTest\Sources\Form\Validator; namespace Combodo\iTop\Test\UnitTest\Sources\Form\Validator;
use Combodo\iTop\Form\Field\StringField; use Combodo\iTop\Form\Field\StringField;
use Combodo\iTop\Form\Validator\MandatoryValidator;
use Combodo\iTop\Test\UnitTest\ItopTestCase; use Combodo\iTop\Test\UnitTest\ItopTestCase;
class ValidatorTest extends ItopTestCase class ValidatorTest extends ItopTestCase
@@ -24,5 +25,7 @@ class ValidatorTest extends ItopTestCase
$bIsMandatoryFieldValidWithNoValue = $oField->Validate(); $bIsMandatoryFieldValidWithNoValue = $oField->Validate();
$this->assertFalse($bIsMandatoryFieldValidWithNoValue); $this->assertFalse($bIsMandatoryFieldValidWithNoValue);
$this->assertNotEmpty($oField->GetErrorMessages()); $this->assertNotEmpty($oField->GetErrorMessages());
$this->assertCount(1, $oField->GetErrorMessages());
$this->assertStringContainsString(MandatoryValidator::DEFAULT_ERROR_MESSAGE, $oField->GetErrorMessages()[0]);
} }
} }