diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php
new file mode 100644
index 0000000000..d7457b481a
--- /dev/null
+++ b/.phpstorm.meta.php
@@ -0,0 +1,16 @@
+ '@',
+ ]));
+ override(\MetaModel::GetObject(0), map([
+ '' => '@',
+ ]));
+}
diff --git a/application/datamodel.application.xml b/application/datamodel.application.xml
index e20a1a95b5..206fbe40a7 100644
--- a/application/datamodel.application.xml
+++ b/application/datamodel.application.xml
@@ -849,5 +849,221 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
+
+
+ Dashlet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{query.selected_class}}
+
+
+
+
+
+
+
+
+
+ bars
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{query.selected_class}}
+
+
+
+
+ {{aggregation_function.value != 'count'}}
+
+
+
+
+ {{query.selected_class}}
+ numeric
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{order_by.value = 'function'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ desc
+
+
+
+
+ Dashlet
+
+
+
+
+ bizmodel
+
+
+
+
+
+
+
+ Dashlet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dashlet
+
+
+
+
+
+
+
+
+
+
+
diff --git a/application/utils.inc.php b/application/utils.inc.php
index ea226e9958..7eea304916 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -1900,6 +1900,12 @@ public static function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &
return $response;
}
+ public static function QuoteForPHP(string $sValue): string
+ {
+ $sEscaped = str_replace(['\\', "'"], ['\\\\', "\\'"], $sValue);
+ return "'$sEscaped'";
+ }
+
/**
* Get a standard list of character sets
*
diff --git a/composer.json b/composer.json
index fba023a2f9..53ddd14f03 100644
--- a/composer.json
+++ b/composer.json
@@ -31,6 +31,7 @@
"symfony/mailer": "^6.4",
"symfony/security-csrf": "^6.4",
"symfony/twig-bundle": "~6.4.0",
+ "symfony/validator" : "^6.4",
"symfony/yaml": "~6.4.0",
"tecnickcom/tcpdf": "^6.6.0",
"thenetworg/oauth2-azure": "^2.0"
diff --git a/composer.lock b/composer.lock
index 6262f6cf6f..19705628ac 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "26fa7aa920057d080bcc0948bf052fda",
+ "content-hash" : "3e627286597661542dd598499c2bcc36",
"packages": [
{
"name": "apereo/phpcas",
@@ -4984,6 +4984,107 @@
"time": "2025-07-10T08:14:14+00:00"
},
{
+ "name" : "symfony/validator",
+ "version" : "v6.4.29",
+ "source" : {
+ "type" : "git",
+ "url" : "https://github.com/symfony/validator.git",
+ "reference" : "99df8a769e64e399f510166141ea74f450e8dd1d"
+ },
+ "dist" : {
+ "type" : "zip",
+ "url" : "https://api.github.com/repos/symfony/validator/zipball/99df8a769e64e399f510166141ea74f450e8dd1d",
+ "reference" : "99df8a769e64e399f510166141ea74f450e8dd1d",
+ "shasum" : ""
+ },
+ "require" : {
+ "php" : ">=8.1",
+ "symfony/deprecation-contracts" : "^2.5|^3",
+ "symfony/polyfill-ctype" : "~1.8",
+ "symfony/polyfill-mbstring" : "~1.0",
+ "symfony/polyfill-php83" : "^1.27",
+ "symfony/translation-contracts" : "^2.5|^3"
+ },
+ "conflict" : {
+ "doctrine/annotations" : "<1.13",
+ "doctrine/lexer" : "<1.1",
+ "symfony/dependency-injection" : "<5.4",
+ "symfony/expression-language" : "<5.4",
+ "symfony/http-kernel" : "<5.4",
+ "symfony/intl" : "<5.4",
+ "symfony/property-info" : "<5.4",
+ "symfony/translation" : "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3",
+ "symfony/yaml" : "<5.4"
+ },
+ "require-dev" : {
+ "doctrine/annotations" : "^1.13|^2",
+ "egulias/email-validator" : "^2.1.10|^3|^4",
+ "symfony/cache" : "^5.4|^6.0|^7.0",
+ "symfony/config" : "^5.4|^6.0|^7.0",
+ "symfony/console" : "^5.4|^6.0|^7.0",
+ "symfony/dependency-injection" : "^5.4|^6.0|^7.0",
+ "symfony/expression-language" : "^5.4|^6.0|^7.0",
+ "symfony/finder" : "^5.4|^6.0|^7.0",
+ "symfony/http-client" : "^5.4|^6.0|^7.0",
+ "symfony/http-foundation" : "^5.4|^6.0|^7.0",
+ "symfony/http-kernel" : "^5.4|^6.0|^7.0",
+ "symfony/intl" : "^5.4|^6.0|^7.0",
+ "symfony/mime" : "^5.4|^6.0|^7.0",
+ "symfony/property-access" : "^5.4|^6.0|^7.0",
+ "symfony/property-info" : "^5.4|^6.0|^7.0",
+ "symfony/translation" : "^5.4.35|~6.3.12|^6.4.3|^7.0.3",
+ "symfony/yaml" : "^5.4|^6.0|^7.0"
+ },
+ "type" : "library",
+ "autoload" : {
+ "psr-4" : {
+ "Symfony\\Component\\Validator\\" : ""
+ },
+ "exclude-from-classmap" : [
+ "/Tests/",
+ "/Resources/bin/"
+ ]
+ },
+ "notification-url" : "https://packagist.org/downloads/",
+ "license" : [
+ "MIT"
+ ],
+ "authors" : [
+ {
+ "name" : "Fabien Potencier",
+ "email" : "fabien@symfony.com"
+ },
+ {
+ "name" : "Symfony Community",
+ "homepage" : "https://symfony.com/contributors"
+ }
+ ],
+ "description" : "Provides tools to validate values",
+ "homepage" : "https://symfony.com",
+ "support" : {
+ "source" : "https://github.com/symfony/validator/tree/v6.4.29"
+ },
+ "funding" : [
+ {
+ "url" : "https://symfony.com/sponsor",
+ "type" : "custom"
+ },
+ {
+ "url" : "https://github.com/fabpot",
+ "type" : "github"
+ },
+ {
+ "url" : "https://github.com/nicolas-grekas",
+ "type" : "github"
+ },
+ {
+ "url" : "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type" : "tidelift"
+ }
+ ],
+ "time" : "2025-11-06T20:26:06+00:00"
+ },
+ {
"name": "symfony/var-dumper",
"version": "v6.4.26",
"source": {
diff --git a/core/designdocument.class.inc.php b/core/designdocument.class.inc.php
index 2e324f38b5..e6fdc0162b 100644
--- a/core/designdocument.class.inc.php
+++ b/core/designdocument.class.inc.php
@@ -41,7 +41,7 @@
/**
* Class \Combodo\iTop\DesignDocument
*
- * A design document is the DOM tree that modelize behaviors. One of its
+ * A design document is the DOM tree that models behaviors. One of its
* characteristics is that it can be altered by the mean of the same kind of document.
*
*/
diff --git a/core/log.class.inc.php b/core/log.class.inc.php
index b79703ebff..0e4962c432 100644
--- a/core/log.class.inc.php
+++ b/core/log.class.inc.php
@@ -691,6 +691,28 @@ public static function MockStaticObjects($oFileLog, $oMetaModelConfig = null)
static::$m_oMockMetaModelConfig = $oMetaModelConfig;
}
+ public static function Exception(string $sMessage, throwable $oException, string $sChannel = null, array $aContext = []): void
+ {
+ $aErrorLogs = [];
+ $aErrorLogs[] = static::PrepareErrorLog($sMessage, $oException, $aContext);
+ $oException = $oException->getPrevious();
+ while ($oException !== null) {
+ $aErrorLogs[] = static::PrepareErrorLog($oException->getMessage(), $oException, $aContext, true);
+ $oException = $oException->getPrevious();
+ }
+ $aErrorLogs = array_reverse($aErrorLogs);
+ foreach ($aErrorLogs as $aErrorLog) {
+ static::Error($aErrorLog['message'], $sChannel, $aErrorLog['context']);
+ }
+ }
+
+ private static function PrepareErrorLog(string $sMessage, throwable $oException, array $aContext, bool $isPrevious = false): array
+ {
+ $aContext['Error Message'] = $oException->getMessage();
+ $aContext['Stack Trace'] = $oException->getTraceAsString();
+ return ['message' => ($isPrevious ? "Previous " : '')."Exception: $sMessage", 'context' => $aContext];
+ }
+
public static function Error($sMessage, $sChannel = null, $aContext = [])
{
static::Log(self::LEVEL_ERROR, $sMessage, $sChannel, $aContext);
diff --git a/core/modelreflection.class.inc.php b/core/modelreflection.class.inc.php
index 3e10c0bdd4..2103e00812 100644
--- a/core/modelreflection.class.inc.php
+++ b/core/modelreflection.class.inc.php
@@ -55,6 +55,11 @@ abstract public function GetParentClass($sClass);
abstract public function GetFiltersList($sClass);
abstract public function IsValidFilterCode($sClass, $sFilterCode);
+ /**
+ * @since 3.3.0
+ */
+ abstract public function IsAbstract($sClass): bool;
+
/**
* @param string $sOQL
*
@@ -148,7 +153,7 @@ public function ListAttributes($sClass, $sScope = null)
$sAttributeClass = get_class($oAttDef);
if ($aScope != null) {
foreach ($aScope as $sScopeClass) {
- if (($sAttributeClass == $sScopeClass) || is_subclass_of($sAttributeClass, $sScopeClass)) {
+ if (is_a($sAttributeClass, $sScopeClass, true)) {
$aAttributes[$sAttCode] = $sAttributeClass;
break;
}
@@ -230,6 +235,11 @@ public function IsValidFilterCode($sClass, $sFilterCode)
return MetaModel::IsValidFilterCode($sClass, $sFilterCode);
}
+ public function IsAbstract($sClass): bool
+ {
+ return MetaModel::IsAbstract($sClass);
+ }
+
public function GetQuery($sOQL)
{
return new QueryReflectionRuntime($sOQL, $this);
diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php
index ef5f829469..0b017791aa 100644
--- a/core/oql/expression.class.inc.php
+++ b/core/oql/expression.class.inc.php
@@ -2103,12 +2103,18 @@ public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitPar
/**
* Evaluate the value of the expression
+ *
* @param array $aArgs
- * @throws \Exception if terms cannot be evaluated as scalars
-*/
+ *
+ * @return mixed
+ * @throws \MissingQueryArgument
+ */
public function Evaluate(array $aArgs)
{
- throw new Exception('not implemented yet');
+ if (!isset($aArgs[$this->m_sName])) {
+ throw new MissingQueryArgument('Missing variable expression argument', array('expecting'=>$this->m_sName));
+ }
+ return $aArgs[$this->m_sName];
}
/**
diff --git a/core/oql/oqlexception.class.inc.php b/core/oql/oqlexception.class.inc.php
index 4df947e4b5..e940d9dbe3 100644
--- a/core/oql/oqlexception.class.inc.php
+++ b/core/oql/oqlexception.class.inc.php
@@ -72,9 +72,15 @@ public function __construct($sIssue, $sInput, $iLine, $iCol, $sUnexpected, $aExp
}
else
{
- $sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
+ $sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput'";
+ if (count($this->m_aExpecting) < 30) {
+ $sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
+ $sMessage .= ', expecting '.json_encode($sExpectations);
+ }
$sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting);
- $sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput', expecting $sExpectations, I would suggest to use '$sSuggest'";
+ if (strlen($sSuggest) > 0) {
+ $sMessage .= ", I would suggest to use ".json_encode($sSuggest);
+ }
}
// make sure everything is assigned properly
@@ -155,5 +161,3 @@ static public function FindClosestString($sInput, $aDictionary)
return $sRet;
}
}
-
-?>
diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php
index 24889d3560..f29899ff48 100644
--- a/core/oql/oqlquery.class.inc.php
+++ b/core/oql/oqlquery.class.inc.php
@@ -57,15 +57,15 @@ public function GetPos()
{
return $this->m_iPos;
}
-
+
public function __toString()
{
return $this->m_sValue;
- }
+ }
}
/**
- *
+ *
* Store hexadecimal values as strings so that we can support 64-bit values
*
*/
@@ -77,12 +77,12 @@ public function __construct($sValue)
{
$this->m_sValue = $sValue;
}
-
+
public function __toString()
{
return $this->m_sValue;
}
-
+
}
class OqlJoinSpec
@@ -109,6 +109,7 @@ public function GetClass()
{
return $this->m_oClass->GetValue();
}
+
public function GetClassAlias()
{
return $this->m_oClassAlias->GetValue();
@@ -118,6 +119,7 @@ public function GetClassDetails()
{
return $this->m_oClass;
}
+
public function GetClassAliasDetails()
{
return $this->m_oClassAlias;
@@ -127,10 +129,12 @@ public function GetLeftField()
{
return $this->m_oLeftField;
}
+
public function GetRightField()
{
return $this->m_oRightField;
}
+
public function GetOperator()
{
return $this->m_sOperator;
@@ -146,8 +150,9 @@ interface CheckableExpression
* @param ModelReflection $oModelReflection MetaModel to consider
* @param array $aAliases Aliases to class names (for the current query)
* @param string $sSourceQuery For the reporting
+ *
* @throws OqlNormalizeException
- */
+ */
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery);
}
@@ -168,13 +173,11 @@ public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuer
$this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
// Only field MATCHES scalar is allowed
- if (!$this->m_oLeftExpr instanceof FieldExpression)
- {
+ if (!$this->m_oLeftExpr instanceof FieldExpression) {
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oLeftExpr->RenderExpression(true), 0));
}
// Only field MATCHES scalar is allowed
- if (!$this->m_oRightExpr instanceof ScalarExpression && !$this->m_oRightExpr instanceof VariableOqlExpression)
- {
+ if (!$this->m_oRightExpr instanceof ScalarExpression && !$this->m_oRightExpr instanceof VariableOqlExpression) {
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oRightExpr->RenderExpression(true), 0));
}
}
@@ -198,7 +201,7 @@ class NestedQueryOqlExpression extends NestedQueryExpression implements Checkabl
*
* @param OQLObjectQuery $oOQLObjectQuery
*/
- public function __construct($oOQLObjectQuery )
+ public function __construct($oOQLObjectQuery)
{
parent::__construct($oOQLObjectQuery->ToDBSearch(""));
$this->m_oOQLObjectQuery = $oOQLObjectQuery;
@@ -232,8 +235,7 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
public function __construct($oName, $oParent = null)
{
- if (is_null($oParent))
- {
+ if (is_null($oParent)) {
$oParent = new OqlName('', 0);
}
$this->m_oParent = $oParent;
@@ -256,37 +258,28 @@ public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuer
{
$sClassAlias = $this->GetParent();
$sFltCode = $this->GetName();
- if (empty($sClassAlias))
- {
+ if (empty($sClassAlias)) {
// Try to find an alias
// Build an array of field => array of aliases
$aFieldClasses = array();
- foreach($aAliases as $sAlias => $sReal)
- {
- foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
- {
+ foreach ($aAliases as $sAlias => $sReal) {
+ foreach ($oModelReflection->GetFiltersList($sReal) as $sAnFltCode) {
$aFieldClasses[$sAnFltCode][] = $sAlias;
}
}
- if (!array_key_exists($sFltCode, $aFieldClasses))
- {
+ if (!array_key_exists($sFltCode, $aFieldClasses)) {
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
}
- if (count($aFieldClasses[$sFltCode]) > 1)
- {
+ if (count($aFieldClasses[$sFltCode]) > 1) {
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
}
$sClassAlias = $aFieldClasses[$sFltCode][0];
- }
- else
- {
- if (!array_key_exists($sClassAlias, $aAliases))
- {
+ } else {
+ if (!array_key_exists($sClassAlias, $aAliases)) {
throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $this->GetParentDetails(), array_keys($aAliases));
}
$sClass = $aAliases[$sClassAlias];
- if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode))
- {
+ if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode)) {
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
}
}
@@ -305,8 +298,7 @@ class ListOqlExpression extends ListExpression implements CheckableExpression
{
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
{
- foreach ($this->GetItems() as $oItemExpression)
- {
+ foreach ($this->GetItems() as $oItemExpression) {
$oItemExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
}
}
@@ -316,8 +308,7 @@ class FunctionOqlExpression extends FunctionExpression implements CheckableExpre
{
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
{
- foreach ($this->GetArgs() as $oArgExpression)
- {
+ foreach ($this->GetArgs() as $oArgExpression) {
$oArgExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
}
}
@@ -350,6 +341,7 @@ abstract public function Check(ModelReflection $oModelReflection, $sSourceQuery)
* Determine the class
*
* @param ModelReflection $oModelReflection MetaModel to consider
+ *
* @return string
* @throws Exception
*/
@@ -392,6 +384,7 @@ public function GetSelectedClasses()
* Determine the class
*
* @param ModelReflection $oModelReflection MetaModel to consider
+ *
* @return string
* @throws Exception
*/
@@ -415,6 +408,7 @@ public function GetClassDetails()
{
return $this->m_oClass;
}
+
public function GetClassAliasDetails()
{
return $this->m_oClassAlias;
@@ -424,6 +418,7 @@ public function GetJoins()
{
return $this->m_aJoins;
}
+
public function GetCondition()
{
return $this->m_oCondition;
@@ -432,44 +427,37 @@ public function GetCondition()
/**
* Recursively check the validity of the expression with regard to the data model
* and the query in which it is used
- *
- * @param ModelReflection $oModelReflection MetaModel to consider
+ *
+ * @param ModelReflection $oModelReflection MetaModel to consider
+ *
* @throws OqlNormalizeException
- */
+ */
public function Check(ModelReflection $oModelReflection, $sSourceQuery, $aParentAliases = array())
{
$sClass = $this->GetClass($oModelReflection);
$sClassAlias = $this->GetClassAlias();
- if (!$oModelReflection->IsValidClass($sClass))
- {
+ if (!$oModelReflection->IsValidClass($sClass)) {
throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
}
- $aAliases = array_merge(array($sClassAlias => $sClass),$aParentAliases);
+ $aAliases = array_merge(array($sClassAlias => $sClass), $aParentAliases);
$aJoinSpecs = $this->GetJoins();
- if (is_array($aJoinSpecs))
- {
- foreach ($aJoinSpecs as $oJoinSpec)
- {
+ if (is_array($aJoinSpecs)) {
+ foreach ($aJoinSpecs as $oJoinSpec) {
$sJoinClass = $oJoinSpec->GetClass();
$sJoinClassAlias = $oJoinSpec->GetClassAlias();
- if (!$oModelReflection->IsValidClass($sJoinClass))
- {
+ if (!$oModelReflection->IsValidClass($sJoinClass)) {
throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses());
}
- if (array_key_exists($sJoinClassAlias, $aAliases))
- {
- if ($sJoinClassAlias != $sJoinClass)
- {
+ if (array_key_exists($sJoinClassAlias, $aAliases)) {
+ if ($sJoinClassAlias != $sJoinClass) {
throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails());
- }
- else
- {
+ } else {
throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails());
}
- }
+ }
// Assumption: ext key on the left only !!!
// normalization should take care of this
@@ -480,85 +468,74 @@ public function Check(ModelReflection $oModelReflection, $sSourceQuery, $aParent
$oRightField = $oJoinSpec->GetRightField();
$sToClass = $oRightField->GetParent();
$sPKeyDescriptor = $oRightField->GetName();
- if ($sPKeyDescriptor != 'id')
- {
+ if ($sPKeyDescriptor != 'id') {
throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id'));
}
$aAliases[$sJoinClassAlias] = $sJoinClass;
- if (!array_key_exists($sFromClass, $aAliases))
- {
+ if (!array_key_exists($sFromClass, $aAliases)) {
throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases));
}
- if (!array_key_exists($sToClass, $aAliases))
- {
+ if (!array_key_exists($sToClass, $aAliases)) {
throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
}
$aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey::class);
$aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeObjectKey::class);
$aAllKeys = array_merge($aExtKeys, $aObjKeys);
- if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
- {
+ if (!array_key_exists($sExtKeyAttCode, $aAllKeys)) {
throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys));
}
- if ($sFromClass == $sJoinClassAlias)
- {
+ if ($sFromClass == $sJoinClassAlias) {
if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
{
$sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
- if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
- {
+ if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
throw new OqlNormalizeException("The joined class ($aAliases[$sFromClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
}
}
- }
- else
- {
+ } else {
$sOperator = $oJoinSpec->GetOperator();
- switch($sOperator)
- {
+ switch ($sOperator) {
case '=':
- $iOperatorCode = TREE_OPERATOR_EQUALS;
- break;
+ $iOperatorCode = TREE_OPERATOR_EQUALS;
+ break;
case 'BELOW':
- $iOperatorCode = TREE_OPERATOR_BELOW;
- break;
+ $iOperatorCode = TREE_OPERATOR_BELOW;
+ break;
case 'BELOW_STRICT':
- $iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
- break;
+ $iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
+ break;
case 'NOT_BELOW':
- $iOperatorCode = TREE_OPERATOR_NOT_BELOW;
- break;
+ $iOperatorCode = TREE_OPERATOR_NOT_BELOW;
+ break;
case 'NOT_BELOW_STRICT':
- $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
- break;
+ $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
+ break;
case 'ABOVE':
- $iOperatorCode = TREE_OPERATOR_ABOVE;
- break;
+ $iOperatorCode = TREE_OPERATOR_ABOVE;
+ break;
case 'ABOVE_STRICT':
- $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
- break;
+ $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
+ break;
case 'NOT_ABOVE':
- $iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
- break;
+ $iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
+ break;
case 'NOT_ABOVE_STRICT':
- $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
- break;
+ $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
+ break;
}
if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
{
$sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
- if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
- {
+ if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
throw new OqlNormalizeException("The joined class ($aAliases[$sToClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
}
}
$aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
$sAttType = $aAttList[$sExtKeyAttCode];
- if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class) && ($sAttType != \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class))
- {
+ if (($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_a($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class, true)) {
throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
}
}
@@ -567,26 +544,23 @@ public function Check(ModelReflection $oModelReflection, $sSourceQuery, $aParent
// Check the select information
//
- foreach ($this->GetSelectedClasses() as $oClassDetails)
- {
+ foreach ($this->GetSelectedClasses() as $oClassDetails) {
$sClassToSelect = $oClassDetails->GetValue();
- if (!array_key_exists($sClassToSelect, $aAliases))
- {
+ if (!array_key_exists($sClassToSelect, $aAliases)) {
throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases));
}
}
// Check the condition tree
//
- if ($this->m_oCondition instanceof Expression)
- {
+ if ($this->m_oCondition instanceof Expression) {
$this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery);
}
}
/**
* Make the relevant DBSearch instance (FromOQL)
- */
+ */
public function ToDBSearch($sQuery)
{
$sClass = $this->GetClass(new ModelReflectionRuntime());
@@ -594,6 +568,7 @@ public function ToDBSearch($sQuery)
$oSearch = new DBObjectSearch($sClass, $sClassAlias);
$oSearch->InitFromOqlQuery($this, $sQuery);
+
return $oSearch;
}
}
@@ -606,19 +581,15 @@ public function __construct(OqlObjectQuery $oLeftQuery, OqlQuery $oRightQueryOrU
{
parent::__construct();
$this->aQueries[] = $oLeftQuery;
- if ($oRightQueryOrUnion instanceof OqlUnionQuery)
- {
- foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery)
- {
+ if ($oRightQueryOrUnion instanceof OqlUnionQuery) {
+ foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery) {
$this->aQueries[] = $oSingleQuery;
}
- }
- else
- {
+ } else {
$this->aQueries[] = $oRightQueryOrUnion;
}
}
-
+
public function GetQueries()
{
return $this->aQueries;
@@ -627,66 +598,54 @@ public function GetQueries()
/**
* Check the validity of the expression with regard to the data model
* and the query in which it is used
- *
- * @param ModelReflection $oModelReflection MetaModel to consider
+ *
+ * @param ModelReflection $oModelReflection MetaModel to consider
+ *
* @throws OqlNormalizeException
- */
+ */
public function Check(ModelReflection $oModelReflection, $sSourceQuery)
{
$aColumnToClasses = array();
- foreach ($this->aQueries as $iQuery => $oQuery)
- {
+ foreach ($this->aQueries as $iQuery => $oQuery) {
$oQuery->Check($oModelReflection, $sSourceQuery);
$aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass($oModelReflection));
$aJoinSpecs = $oQuery->GetJoins();
- if (is_array($aJoinSpecs))
- {
- foreach ($aJoinSpecs as $oJoinSpec)
- {
+ if (is_array($aJoinSpecs)) {
+ foreach ($aJoinSpecs as $oJoinSpec) {
$aAliasToClass[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass();
}
}
$aSelectedClasses = $oQuery->GetSelectedClasses();
- if ($iQuery != 0)
- {
- if (count($aSelectedClasses) < count($aColumnToClasses))
- {
+ if ($iQuery != 0) {
+ if (count($aSelectedClasses) < count($aColumnToClasses)) {
$oLastClass = end($aSelectedClasses);
throw new OqlNormalizeException('Too few selected classes in the subquery', $sSourceQuery, $oLastClass);
}
- if (count($aSelectedClasses) > count($aColumnToClasses))
- {
+ if (count($aSelectedClasses) > count($aColumnToClasses)) {
$oLastClass = end($aSelectedClasses);
throw new OqlNormalizeException('Too many selected classes in the subquery', $sSourceQuery, $oLastClass);
}
}
- foreach ($aSelectedClasses as $iColumn => $oClassDetails)
- {
+ foreach ($aSelectedClasses as $iColumn => $oClassDetails) {
$sAlias = $oClassDetails->GetValue();
$sClass = $aAliasToClass[$sAlias];
$aColumnToClasses[$iColumn][] = array(
- 'alias' => $sAlias,
- 'class' => $sClass,
+ 'alias' => $sAlias,
+ 'class' => $sClass,
'class_name' => $oClassDetails,
);
}
}
- foreach ($aColumnToClasses as $iColumn => $aClasses)
- {
+ foreach ($aColumnToClasses as $iColumn => $aClasses) {
$sRootClass = null;
- foreach ($aClasses as $iQuery => $aData)
- {
- if ($iQuery == 0)
- {
+ foreach ($aClasses as $iQuery => $aData) {
+ if ($iQuery == 0) {
// Establish the reference
$sRootClass = $oModelReflection->GetRootClass($aData['class']);
- }
- else
- {
- if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass)
- {
+ } else {
+ if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass) {
$aSubclasses = $oModelReflection->EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL);
throw new OqlNormalizeException('Incompatible classes: could not find a common ancestor', $sSourceQuery, $aData['class_name'], $aSubclasses);
}
@@ -699,21 +658,21 @@ public function Check(ModelReflection $oModelReflection, $sSourceQuery)
* Determine the class
*
* @param ModelReflection $oModelReflection MetaModel to consider
+ *
* @return string
* @throws Exception
*/
public function GetClass(ModelReflection $oModelReflection)
{
$aFirstColClasses = array();
- foreach ($this->aQueries as $iQuery => $oQuery)
- {
+ foreach ($this->aQueries as $iQuery => $oQuery) {
$aFirstColClasses[] = $oQuery->GetClass($oModelReflection);
}
$sClass = self::GetLowestCommonAncestor($oModelReflection, $aFirstColClasses);
- if (is_null($sClass))
- {
+ if (is_null($sClass)) {
throw new Exception('Could not determine the class of the union query. This issue should have been detected earlier by calling OqlQuery::Check()');
}
+
return $sClass;
}
@@ -726,6 +685,7 @@ public function GetClass(ModelReflection $oModelReflection)
public function GetClassAlias()
{
$sAlias = $this->aQueries[0]->GetClassAlias();
+
return $sAlias;
}
@@ -735,29 +695,25 @@ public function GetClassAlias()
*
* @param ModelReflection $oModelReflection MetaModel to consider
* @param array $aClasses Flat list of classes
+ *
* @return string the lowest common ancestor amongst classes, null if none has been found
* @throws Exception
*/
public static function GetLowestCommonAncestor(ModelReflection $oModelReflection, $aClasses)
{
$sAncestor = null;
- foreach($aClasses as $sClass)
- {
- if (is_null($sAncestor))
- {
+ foreach ($aClasses as $sClass) {
+ if (is_null($sAncestor)) {
// first loop
$sAncestor = $sClass;
- }
- elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor))
- {
+ } elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor)) {
$sAncestor = null;
break;
- }
- else
- {
+ } else {
$sAncestor = self::LowestCommonAncestor($oModelReflection, $sAncestor, $sClass);
}
}
+
return $sAncestor;
}
@@ -766,37 +722,32 @@ public static function GetLowestCommonAncestor(ModelReflection $oModelReflection
*/
protected static function LowestCommonAncestor(ModelReflection $oModelReflection, $sClassA, $sClassB)
{
- if ($sClassA == $sClassB)
- {
+ if ($sClassA == $sClassB) {
$sRet = $sClassA;
- }
- elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB)))
- {
+ } elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB))) {
$sRet = $sClassB;
- }
- elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA)))
- {
+ } elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA))) {
$sRet = $sClassA;
- }
- else
- {
+ } else {
// Recurse
$sRet = self::LowestCommonAncestor($oModelReflection, $sClassA, $oModelReflection->GetParentClass($sClassB));
}
+
return $sRet;
}
+
/**
* Make the relevant DBSearch instance (FromOQL)
- */
+ */
public function ToDBSearch($sQuery)
{
$aSearches = array();
- foreach ($this->aQueries as $oQuery)
- {
+ foreach ($this->aQueries as $oQuery) {
$aSearches[] = $oQuery->ToDBSearch($sQuery);
}
$oSearch = new DBUnionSearch($aSearches);
+
return $oSearch;
}
}
diff --git a/css/backoffice/blocks-integrations/field/_field-with-field.scss b/css/backoffice/blocks-integrations/field/_field-with-field.scss
index fb2d7ea3e5..73709adbef 100644
--- a/css/backoffice/blocks-integrations/field/_field-with-field.scss
+++ b/css/backoffice/blocks-integrations/field/_field-with-field.scss
@@ -4,7 +4,7 @@
*/
$ibo-field--spacing-top--with-same-block: $ibo-spacing-500 !default;
-.ibo-field + .ibo-field {
+.ibo-field + .ibo-field:not(:empty) {
margin-top: $ibo-field--spacing-top--with-same-block;
}
diff --git a/css/backoffice/components/_form.scss b/css/backoffice/components/_form.scss
index d80fdd96a2..c732bebd8f 100644
--- a/css/backoffice/components/_form.scss
+++ b/css/backoffice/components/_form.scss
@@ -6,4 +6,72 @@
.ibo-prop-header {
@extend %ibo-font-size-150;
padding-bottom: 14px;
-}
\ No newline at end of file
+}
+
+.help-text{
+ padding: 1px 5px;
+ background-color: #d7e3f8;
+ border: 1px solid #c6e7f5;
+ border-radius: 5px;
+ margin: 5px 0;
+ font-size: 0.9em;
+}
+
+.form-error ul{
+ padding: 1px 5px;
+ background-color: #f8d7da;
+ border: 1px solid #f5c6cb;
+ border-radius: 5px;
+ margin: 5px 0;
+ font-size: 0.9em;
+}
+
+.subform{
+ background-color: #efefef;
+ border-radius: 5px;
+ padding: 10px;
+}
+
+.form-buttons{
+ margin: 20px 0;
+}
+
+.form select{
+ padding: 0;
+ overflow-y: auto;
+}
+
+.form select option{
+ height: 30px;
+ display: flex;
+ align-items: center;
+}
+
+.turbo-refreshing{
+ opacity: .5;
+}
+
+.ibo-field legend{
+ margin-top: 24px;
+}
+
+collection-entry-element {
+ margin-top: 8px;
+ display: block;
+ padding: 10px 10px;
+ background-color: #f5f5f5;
+ border-radius: 5px;
+}
+.ts-control{
+ height: auto;
+ min-height: 30px;
+}
+
+.ibo-form-actions > .ibo-button > span{
+ margin-right: 5px;
+}
+
+.ibo-form textarea{
+ resize: vertical;
+}
+
diff --git a/css/backoffice/vendors/_tomselect.scss b/css/backoffice/vendors/_tomselect.scss
new file mode 100644
index 0000000000..74cdeddf2e
--- /dev/null
+++ b/css/backoffice/vendors/_tomselect.scss
@@ -0,0 +1,3 @@
+@import "../../../node_modules/tom-select/dist/scss/tom-select.scss";
+
+$select-color-item-active-border: $ibo-input--focus--border-color;
\ No newline at end of file
diff --git a/data/.compilation-symlinks b/data/.compilation-symlinks
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php b/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php
index a2a33fd89e..02ee530a22 100644
--- a/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php
+++ b/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php
@@ -30,7 +30,7 @@
// Identification
//
'label' => 'Database maintenance tools',
- 'category' => 'business',
+ 'category' => 'Application management',
// Setup
//
diff --git a/datamodels/2.x/itop-config/src/Controller/ConfigEditorController.php b/datamodels/2.x/itop-config/src/Controller/ConfigEditorController.php
index c45c4f0157..aba7d2eb86 100644
--- a/datamodels/2.x/itop-config/src/Controller/ConfigEditorController.php
+++ b/datamodels/2.x/itop-config/src/Controller/ConfigEditorController.php
@@ -28,6 +28,7 @@ class ConfigEditorController extends Controller
public function __construct()
{
parent::__construct(MODULESROOT.static::MODULE_NAME.'/templates', static::MODULE_NAME);
+ $this->SetDebugAllowed(false);
}
public function OperationEdit(): void
diff --git a/datamodels/2.x/itop-core-update/module.itop-core-update.php b/datamodels/2.x/itop-core-update/module.itop-core-update.php
index 9e4272e342..d243de6871 100644
--- a/datamodels/2.x/itop-core-update/module.itop-core-update.php
+++ b/datamodels/2.x/itop-core-update/module.itop-core-update.php
@@ -30,7 +30,7 @@
// Identification
//
'label' => 'iTop Core Update',
- 'category' => 'business',
+ 'category' => 'Application management',
// Setup
//
diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php
index 2f6b66450f..e3949b0fa4 100644
--- a/dictionaries/en.dictionary.itop.ui.php
+++ b/dictionaries/en.dictionary.itop.ui.php
@@ -1333,6 +1333,7 @@
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Day of month for %1$s',
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hour)',
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (month)',
+ 'UI:DashletGroupBy:Prop-GroupBy:Select-Year' => '%1$s (year)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (day of week)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (day of month)',
'UI:DashletGroupBy:MissingGroupBy' => 'Please select the field on which the objects will be grouped together',
@@ -1653,6 +1654,8 @@
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s',
'UI:StateChanged' => 'State changed',
+
+ 'UI:AddSubTree' => 'Add entry',
]);
//
diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php
index c6d3782fb2..9fe2da13ce 100644
--- a/dictionaries/fr.dictionary.itop.ui.php
+++ b/dictionaries/fr.dictionary.itop.ui.php
@@ -1279,6 +1279,7 @@
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Jour du mois pour %1$s',
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (heure)',
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mois)',
+ 'UI:DashletGroupBy:Prop-GroupBy:Select-Year' => '%1$s (année)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (jour de la semaine)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (jour du mois)',
'UI:DashletGroupBy:MissingGroupBy' => 'Veuillez sélectionner le champ sur lequel les objets seront groupés',
@@ -1544,7 +1545,8 @@
'UI:Search:Criteria:HierarchicalKey:ChildrenIncluded:Hint' => 'Les descendants des objets sélectionnés seront inclus.',
'UI:Search:Criteria:Raw:Filtered' => 'Filtré',
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtré sur %1$s',
- 'UI:StateChanged' => 'Etat modifié',
+ 'UI:StateChanged' => 'État modifié',
+ 'UI:AddSubTree' => 'Ajouter une entrée',
]);
//
diff --git a/dictionaries/ui/components/input/en.dictionary.itop.input.php b/dictionaries/ui/components/input/en.dictionary.itop.input.php
index 7cc0e228d4..e7acd64b46 100644
--- a/dictionaries/ui/components/input/en.dictionary.itop.input.php
+++ b/dictionaries/ui/components/input/en.dictionary.itop.input.php
@@ -19,8 +19,15 @@
*/
// Input
-Dict::Add('EN US', 'English', 'English', [
- 'UI:Component:Input:ChangeNotAllowed' => 'This change is not allowed',
- 'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match',
- 'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s item(s) required',
-]);
+Dict::Add(
+ 'EN US',
+ 'English',
+ 'English',
+ [
+ 'UI:Component:Input:ChangeNotAllowed' => 'This change is not allowed',
+ 'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match',
+ 'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s item(s) required',
+
+ 'UI:Component:Input:Select:Select_item' => 'Select an item...',
+ ]
+);
diff --git a/dictionaries/ui/components/input/fr.dictionary.itop.input.php b/dictionaries/ui/components/input/fr.dictionary.itop.input.php
index c17c791125..eb8dd5f61a 100644
--- a/dictionaries/ui/components/input/fr.dictionary.itop.input.php
+++ b/dictionaries/ui/components/input/fr.dictionary.itop.input.php
@@ -10,8 +10,15 @@
/**
*
*/
-Dict::Add('FR FR', 'French', 'Français', [
- 'UI:Component:Input:ChangeNotAllowed' => 'Cette modification n\'est pas autorisée',
- 'UI:Component:Input:Password:DoesNotMatch' => 'Les mots de passe ne correspondent pas',
- 'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s élément(s) requis',
-]);
+Dict::Add(
+ 'FR FR',
+ 'French',
+ 'Français',
+ [
+ 'UI:Component:Input:ChangeNotAllowed' => 'Cette modification n\'est pas autorisée',
+ 'UI:Component:Input:Password:DoesNotMatch' => 'Les mots de passe ne correspondent pas',
+ 'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s élément(s) requis',
+
+ 'UI:Component:Input:Select:Select_item' => 'Sélectionnez un élément...',
+ ]
+);
diff --git a/js/forms/choices_element.js b/js/forms/choices_element.js
new file mode 100644
index 0000000000..eb1dd9133e
--- /dev/null
+++ b/js/forms/choices_element.js
@@ -0,0 +1,45 @@
+class ChoicesElement extends HTMLSelectElement {
+
+ // register the custom element
+ static {
+ customElements.define('choices-element', ChoicesElement, {extends: 'select'});
+ }
+
+ plugins = [];
+ connectedCallback() {
+
+ if (this.tomselect) {
+ return;
+ }
+
+ if (this.getAttribute('multiple')) {
+ this.plugins.push('remove_button');
+ }
+
+ const options = {
+ plugins: this.plugins,
+ wrapperClass: 'ts-wrapper ibo-input-wrapper ibo-input-select-wrapper--with-buttons ibo-input-select-autocomplete-wrapper',
+ controlClass: 'ts-control ibo-input ibo-input-select ibo-input-select-autocomplete',
+ dropdownParent: 'body',
+ render: {
+ dropdown: function (data, escape) {
+ return `
`;
+ }
+ }
+ };
+
+ if (this.getAttribute('data-tom-select-disable-auto-complete')) {
+ // options.controlInput = null;
+ }
+ if (this.getAttribute('data-tom-select-max-items-selected') && this.getAttribute('data-tom-select-max-items-selected') !== '') {
+ options.maxItems = parseInt(this.getAttribute('data-tom-select-max-items-selected'));
+ }
+ if (this.getAttribute('data-tom-select-placehelder')) {
+ options.placeholder = this.getAttribute('data-tom-select-placehelder');
+ }
+
+ new TomSelect(this, options);
+ }
+}
+
+
diff --git a/js/forms/collection_element.js b/js/forms/collection_element.js
new file mode 100644
index 0000000000..5a32d86661
--- /dev/null
+++ b/js/forms/collection_element.js
@@ -0,0 +1,39 @@
+class CollectionElement extends HTMLElement {
+
+ #eBtnAdd;
+
+ // register the custom element
+ static {
+ customElements.define('collection-element', CollectionElement);
+ }
+
+ addFormToCollection(e) {
+ const collectionHolder = document.querySelector('.'+e.currentTarget.dataset.collectionHolderClass);
+ const item = document.createElement('div');
+
+ const collectionHolderList = collectionHolder.querySelector('[role="list"]');
+
+ item.innerHTML = collectionHolder
+ .dataset
+ .prototype
+ .replace(
+ /__name__/g,
+ collectionHolder.dataset.index
+ );
+
+ collectionHolderList.appendChild(item.firstChild);
+ collectionHolder.dataset.index++;
+
+ this.querySelectorAll('collection-entry-element').forEach((entry) => {
+ console.log('test');
+ entry.updateButtonStates();
+ });
+ }
+
+ /** connectedCallback **/
+ connectedCallback() {
+ this.#eBtnAdd = this.querySelector('.add_item_link');
+ this.#eBtnAdd.addEventListener('click', this.addFormToCollection.bind(this));
+ }
+
+}
diff --git a/js/forms/collection_entry_element.js b/js/forms/collection_entry_element.js
new file mode 100644
index 0000000000..190aa5300e
--- /dev/null
+++ b/js/forms/collection_entry_element.js
@@ -0,0 +1,112 @@
+class CollectionEntryElement extends HTMLElement {
+
+ // Button elements
+ #eBtnDelete;
+ #eBtnMoveUp;
+ #eBtnMoveDown;
+
+ // register the custom element
+ static {
+ customElements.define('collection-entry-element', CollectionEntryElement);
+ }
+
+ /** connectedCallback **/
+ connectedCallback() {
+
+ if ((this.dataset.new || this.dataset.allowDelete) && this.#eBtnDelete === undefined) {
+ this.#eBtnDelete = this.#createButton('Remove', 'ibo-button ibo-is-regular ibo-is-danger');
+ this.#eBtnDelete.addEventListener('click', this.#removeCollectionItem.bind(this));
+ this.appendChild(this.#eBtnDelete);
+ }
+
+ if (this.dataset.allowOrdering) {
+ if (this.#eBtnMoveUp === undefined) {
+ this.#eBtnMoveUp = this.#createButton('Move Up', 'ibo-button ibo-is-regular');
+ this.#eBtnMoveUp.addEventListener('click', this.#moveUp.bind(this));
+ this.appendChild(this.#eBtnMoveUp);
+ }
+ if (this.#eBtnMoveDown === undefined) {
+ this.#eBtnMoveDown = this.#createButton('Move Down', 'ibo-button ibo-is-regular');
+ this.#eBtnMoveDown.addEventListener('click', this.#moveDown.bind(this));
+ this.appendChild(this.#eBtnMoveDown);
+ }
+ }
+
+ this.updateButtonStates();
+ }
+
+ /**
+ * Update the state of the buttons (enabled/disabled).
+ *
+ */
+ updateButtonStates() {
+
+ if (this.dataset.allowOrdering) {
+
+ if (this.previousElementSibling === null) {
+ this.#eBtnMoveUp.setAttribute('disabled', 'disabled');
+ } else {
+ this.#eBtnMoveUp.removeAttribute('disabled');
+ }
+
+ if (this.nextElementSibling === null) {
+ this.#eBtnMoveDown.setAttribute('disabled', 'disabled');
+ } else {
+ this.#eBtnMoveDown.removeAttribute('disabled');
+ }
+
+ }
+
+ }
+
+ /**
+ * Create a button element.
+ *
+ * @param label
+ * @param className
+ * @returns {HTMLButtonElement}
+ */
+ #createButton(label, className) {
+
+ const btnElement = document.createElement('button');
+ btnElement.type = 'button';
+ btnElement.className = className;
+ btnElement.textContent = label;
+
+ return btnElement;
+ }
+
+ /**
+ * Move this collection item up.
+ *
+ */
+ #moveUp() {
+ const prev = this.previousElementSibling;
+ if (prev) {
+ this.parentNode.insertBefore(this, prev);
+ this.updateButtonStates();
+ prev.updateButtonStates();
+ }
+ }
+
+ /**
+ * Move this collection item down.
+ *
+ */
+ #moveDown() {
+ const next = this.nextElementSibling;
+ if (next) {
+ this.parentNode.insertBefore(next, this);
+ this.updateButtonStates();
+ next.updateButtonStates();
+ }
+ }
+
+ /**
+ * Remove this collection item.
+ *
+ */
+ #removeCollectionItem() {
+ this.remove();
+ }
+}
diff --git a/js/forms/form_element.js b/js/forms/form_element.js
new file mode 100644
index 0000000000..679b0cf067
--- /dev/null
+++ b/js/forms/form_element.js
@@ -0,0 +1,95 @@
+class FormElement extends HTMLFormElement
+{
+ static #TURBO_REFRESHING_CLASS = 'turbo-refreshing';
+ static #TURBO_TRIGGER_FIELD = '_turbo_trigger';
+
+ #aFormBlockDataTransmittedData = {};
+
+ // register the custom element
+ static {
+ customElements.define('itop-form-element', FormElement, {extends: 'form'});
+ }
+
+ TriggerTurbo(oElement) {
+
+ // Get the name and id of the element triggering turbo
+ const sName = oElement.getAttribute('name');
+ const sId = oElement.getAttribute('id');
+
+ if(FormElement.IsCheckbox(oElement) || this.#aFormBlockDataTransmittedData[sName] !== oElement.value) {
+
+ // Refresh UI
+ this.#StartRefreshingUI(sId);
+
+ // Pre Submit
+ this.#PreSubmitTurboForm(sName);
+
+ // Submit
+ oElement.form.requestSubmit();
+
+ // Post Submit
+ this.#PostSubmitTurboForm(sName)
+
+ this.#aFormBlockDataTransmittedData[sName] = oElement.value;
+ }
+
+ }
+
+ /**
+ * Start refreshing UI.
+ *
+ * @param sId
+ * @constructor
+ */
+ #StartRefreshingUI(sId)
+ {
+ Array.from(this.querySelectorAll(`.ibo-content-block`)).forEach(block => {
+ if(block.dataset.impactedBy !== undefined){
+ const aImpactedBy = block.dataset.impactedBy.split(',');
+ if(aImpactedBy.includes(sId)){
+ block.classList.add(FormElement.#TURBO_REFRESHING_CLASS);
+ }
+ }
+ });
+ }
+
+ /**
+ * Pre submit the form.
+ * Set the turbo trigger field in the form and disable validation
+ *
+ * @param sName
+ * @constructor
+ */
+ #PreSubmitTurboForm(sName)
+ {
+ this.querySelector(`[name="${this.getAttribute("name")}[${FormElement.#TURBO_TRIGGER_FIELD}]"]`).value = sName;
+ this.setAttribute('novalidate', true);
+ }
+
+ /**
+ * Post submit the form.
+ * Reset the turbo trigger field and restore form validation.
+ *
+ * @param sName
+ * @constructor
+ */
+ #PostSubmitTurboForm(sName)
+ {
+ this.querySelector(`[name="${this.getAttribute("name")}[${FormElement.#TURBO_TRIGGER_FIELD}]"]`).value = null;
+ this.removeAttribute('novalidate');
+ }
+
+ /**
+ *
+ * @param oElement
+ * @returns {boolean}
+ */
+ static IsCheckbox (oElement)
+ {
+ return oElement instanceof HTMLInputElement
+ && oElement.getAttribute('type') === 'checkbox'
+ }
+
+}
+
+
diff --git a/js/forms/oql_element.js b/js/forms/oql_element.js
new file mode 100644
index 0000000000..b2350e1595
--- /dev/null
+++ b/js/forms/oql_element.js
@@ -0,0 +1,123 @@
+class OqlElement extends HTMLTextAreaElement {
+
+ static #DEBONCE = 400;
+
+ // register the custom element
+ static{
+ customElements.define('oql-element', OqlElement, {extends: 'textarea'});
+ }
+
+ // variables
+ #url = '../pages/ajax.render.php?route=oql.validate_query';
+ #iconValid = 'fa-check-double';
+ #iconNotValid = 'fa-exclamation-triangle';
+ #debounceTimer = null;
+ #debounce = OqlElement.#DEBONCE;
+
+ /** connectedCallback **/
+ connectedCallback() {
+ this.addEventListener('input', this.#onInput.bind(this));
+ this.#callValidateQuery();
+
+ this.addEventListener('focus', this.#onFocus.bind(this));
+
+ const oBtnBook = this.closest('.ibo-content-block').querySelector('[data-role="ibo-button"][data-action="book"]');
+ oBtnBook.addEventListener('click', this.#search.bind(this))
+
+ const oBtnRun = this.closest('.ibo-content-block').querySelector('[data-role="ibo-button"][data-action="run"]');
+ oBtnRun.addEventListener('click', this.#run.bind(this))
+ }
+
+ /**
+ * Call oql verification with debounce when input event is fired.
+ */
+ #onInput() {
+ if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
+ this.#debounceTimer = setTimeout(() => {
+ this.#callValidateQuery(true);
+ }, this.#debounce);
+ }
+
+ /**
+ * Call oql verification with debounce when focus event is fired.
+ */
+ #onFocus() {
+ this.#callValidateQuery();
+ }
+
+ /**
+ * Call the ajax to validate the query.
+ *
+ * @param fireChange flag to handle change event
+ */
+ #callValidateQuery(fireChange = false) {
+
+ fetch(this.#url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Combodo-Ajax': true
+ },
+ body: JSON.stringify({
+ query: this.value
+ })
+ })
+ .then(response => response.json())
+ .then(response => {
+ // fire change event only if the query is valid
+ if (fireChange && response.is_valid){
+ this.#fireChangeEvent();
+ }
+ // update the icon color
+ const fieldEl = this.closest('.ibo-field');
+ const marqueeEl = fieldEl.querySelector('[role="marquee"]');
+ marqueeEl.style.color = response.is_valid ? 'green' : 'orange';
+ marqueeEl.classList.toggle(this.#iconNotValid, !response.is_valid);
+ marqueeEl.classList.toggle(this.#iconValid, response.is_valid);
+ marqueeEl.setAttribute('title', response.is_valid ? Dict.S(this.dataset.validQueryText) : Dict.S(this.dataset.invalidQueryText));
+ });
+ }
+
+ /**
+ * Fire a change event.
+ */
+ #fireChangeEvent() {
+ const changeEvent = new Event('change', { bubbles: true, cancelable: true });
+ this.dispatchEvent(changeEvent);
+ }
+
+ #search(){
+ const sId = this.getAttribute('id');
+ const sDialogId = `ac_dlg_${sId}`;
+
+ const sModalTitle = Dict.S(this.dataset.modalTitleText);
+ const sEmptyText = Dict.S(this.dataset.emptyText);
+
+ // Instance the widget
+ const oACWidget = new ExtKeyWidget(sId, 'QueryOQL', 'SELECT QueryOQL WHERE is_template = \'yes\'', sModalTitle, true, null, null, true, true, 'oql');
+ oACWidget.emptyHtml = ``;
+
+ // Store in window to be accessible from dialog
+ window[`oACWidget_${sId}`] = oACWidget;
+
+ // Open the dialog
+ if ($(`#${sDialogId}`).length === 0)
+ {
+ $('body').append(``);
+ $(`#${sDialogId}`).dialog({
+ width: $(window).width()*0.8,
+ height: $(window).height()*0.8,
+ autoOpen: false,
+ modal: true,
+ resizeStop: oACWidget.UpdateSizes,
+ });
+ }
+
+ // Start searching
+ oACWidget.Search();
+ }
+
+ #run(){
+ window.open('../pages/run_query.php?expression=' + encodeURI(this.value), '_blank');
+ }
+}
diff --git a/js/forms/turbo_stream_event_element.js b/js/forms/turbo_stream_event_element.js
new file mode 100644
index 0000000000..d440b39e66
--- /dev/null
+++ b/js/forms/turbo_stream_event_element.js
@@ -0,0 +1,26 @@
+class TurboStreamEvent extends HTMLElement {
+
+ // register the custom element
+ static {
+ customElements.define('turbo-stream-event', TurboStreamEvent);
+ }
+
+ constructor() {
+ super();
+
+ this.style.display = 'none';
+
+ const event = new CustomEvent("itop:TurboStreamEvent", {
+ detail: {
+ id: this.getAttribute('id'),
+ form_id: this.dataset.formId,
+ block_class: this.dataset.formBlockClass,
+ view_data: this.dataset.viewData,
+ valid: this.dataset.valid,
+ },
+ });
+
+ document.dispatchEvent(event);
+ }
+
+}
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index c338b2bcc1..977cf54b2b 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -244,6 +244,10 @@
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Toolbar\\ToolbarSpacer\\ToolbarSpacer' => $baseDir . '/sources/Application/UI/Base/Component/Toolbar/ToolbarSpacer/ToolbarSpacer.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Toolbar\\ToolbarSpacer\\ToolbarSpacerUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Toolbar/ToolbarSpacer/ToolbarSpacerUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Toolbar\\ToolbarUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Toolbar/ToolbarUIBlockFactory.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboForm\\TurboForm' => $baseDir . '/sources/Application/UI/Base/Component/TurboForm/TurboForm.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboForm\\TurboFormUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/TurboForm/TurboFormUIBlockFactory.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboUpdate\\TurboStream' => $baseDir . '/sources/Application/UI/Base/Component/TurboStream/TurboStream.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboUpdate\\TurboStreamUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/TurboStream/TurboStreamUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntry' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/ActivityEntry.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntryFactory' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/ActivityEntryFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\CMDBChangeOp\\CMDBChangeOpAttachmentAddedFactory' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/CMDBChangeOp/CMDBChangeOpAttachmentAddedFactory.php',
@@ -341,6 +345,7 @@
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => $baseDir . '/sources/Controller/Base/Layout/ActivityPanelController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => $baseDir . '/sources/Controller/Base/Layout/ObjectController.php',
+ 'Combodo\\iTop\\Controller\\Base\\Layout\\OqlController' => $baseDir . '/sources/Controller/Base/Layout/OqlController.php',
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => $baseDir . '/sources/Controller/Links/LinkSetController.php',
'Combodo\\iTop\\Controller\\Newsroom\\iTopNewsroomController' => $baseDir . '/sources/Controller/Newsroom/iTopNewsroomController.php',
'Combodo\\iTop\\Controller\\Notifications\\ActionController' => $baseDir . '/sources/Controller/Notifications/ActionController.php',
@@ -473,8 +478,103 @@
'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\\Forms\\Block\\AbstractFormBlock' => $baseDir . '/sources/Forms/Block/AbstractFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\AbstractTypeFormBlock' => $baseDir . '/sources/Forms/Block/AbstractTypeFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\CheckboxFormBlock' => $baseDir . '/sources/Forms/Block/Base/CheckboxFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => $baseDir . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFromInputsBlock' => $baseDir . '/sources/Forms/Block/Base/ChoiceFromInputsBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\CollectionBlock' => $baseDir . '/sources/Forms/Block/Base/CollectionBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\DateFormBlock' => $baseDir . '/sources/Forms/Block/Base/DateFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\DateTimeFormBlock' => $baseDir . '/sources/Forms/Block/Base/DateTimeFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => $baseDir . '/sources/Forms/Block/Base/FormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\HiddenFormBlock' => $baseDir . '/sources/Forms/Block/Base/HiddenFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\IntegerFormBlock' => $baseDir . '/sources/Forms/Block/Base/IntegerFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\NumberFormBlock' => $baseDir . '/sources/Forms/Block/Base/NumberFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextAreaFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => $baseDir . '/sources/Forms/Block/Base/TextFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeTypeChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeTypeChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\Dashlet\\AggregateFunctionFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/Dashlet/AggregateFunctionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\Dashlet\\ClassAttributeGroupByFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/Dashlet/ClassAttributeGroupByFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\LabelFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/LabelFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => $baseDir . '/sources/Forms/Block/DataModel/OqlFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\AbstractExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/AbstractExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\BooleanExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/BooleanExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\NumberExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/NumberExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\StringExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/StringExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\FormBlockException' => $baseDir . '/sources/Forms/Block/FormBlockException.php',
+ 'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => $baseDir . '/sources/Forms/Block/FormBlockHelper.php',
+ 'Combodo\\iTop\\Forms\\Block\\FormBlockService' => $baseDir . '/sources/Forms/Block/FormBlockService.php',
+ 'Combodo\\iTop\\Forms\\Block\\IFormBlock' => $baseDir . '/sources/Forms/Block/IFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Compiler\\FormsCompiler' => $baseDir . '/sources/Forms/Compiler/FormsCompiler.php',
+ 'Combodo\\iTop\\Forms\\Compiler\\FormsCompilerException' => $baseDir . '/sources/Forms/Compiler/FormsCompilerException.php',
+ 'Combodo\\iTop\\Forms\\Controller\\FormsController' => $baseDir . '/sources/Forms/Controller/FormsController.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => $baseDir . '/sources/Forms/FormBuilder/DependencyHandler.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => $baseDir . '/sources/Forms/FormBuilder/DependencyMap.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => $baseDir . '/sources/Forms/FormBuilder/FormBuilder.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilderException' => $baseDir . '/sources/Forms/FormBuilder/FormBuilderException.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormHelper' => $baseDir . '/sources/Forms/FormBuilder/FormHelper.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormTypeExtension' => $baseDir . '/sources/Forms/FormBuilder/FormTypeExtension.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormType' => $baseDir . '/sources/Forms/FormBuilder/ResolvedFormType.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => $baseDir . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php',
+ 'Combodo\\iTop\\Forms\\FormType\\Base\\ChoiceFormType' => $baseDir . '/sources/Forms/FormType/Base/ChoiceFormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\Base\\CollectionFormType' => $baseDir . '/sources/Forms/FormType/Base/CollectionFormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\Base\\FormType' => $baseDir . '/sources/Forms/FormType/Base/FormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\DataModel\\OqlFormType' => $baseDir . '/sources/Forms/FormType/DataModel/OqlFormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\FormTypeHelper' => $baseDir . '/sources/Forms/FormType/FormTypeHelper.php',
+ 'Combodo\\iTop\\Forms\\Forms' => $baseDir . '/sources/Forms/Forms.php',
+ 'Combodo\\iTop\\Forms\\FormsException' => $baseDir . '/sources/Forms/FormsException.php',
+ 'Combodo\\iTop\\Forms\\IO\\AbstractFormIO' => $baseDir . '/sources/Forms/IO/AbstractFormIO.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\AbstractConverter' => $baseDir . '/sources/Forms/IO/Converter/AbstractConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\ChoiceValueToLabelConverter' => $baseDir . '/sources/Forms/IO/Converter/ChoiceValueToLabelConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\CollectionToCountConverter' => $baseDir . '/sources/Forms/IO/Converter/CollectionToCountConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\OqlToClassConverter' => $baseDir . '/sources/Forms/IO/Converter/OqlToClassConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormBinding' => $baseDir . '/sources/Forms/IO/FormBinding.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormBlockIOException' => $baseDir . '/sources/Forms/IO/FormBlockIOException.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormInput' => $baseDir . '/sources/Forms/IO/FormInput.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormOutput' => $baseDir . '/sources/Forms/IO/FormOutput.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AbstractIOFormat' => $baseDir . '/sources/Forms/IO/Format/AbstractIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AttributeIOFormat' => $baseDir . '/sources/Forms/IO/Format/AttributeIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AttributeTypeArrayIOFormat' => $baseDir . '/sources/Forms/IO/Format/AttributeTypeArrayIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AttributeTypeIOFormat' => $baseDir . '/sources/Forms/IO/Format/AttributeTypeIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\BooleanIOFormat' => $baseDir . '/sources/Forms/IO/Format/BooleanIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\ClassIOFormat' => $baseDir . '/sources/Forms/IO/Format/ClassIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\IntegerIOFormat' => $baseDir . '/sources/Forms/IO/Format/IntegerIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\NumberIOFormat' => $baseDir . '/sources/Forms/IO/Format/NumberIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\StringIOFormat' => $baseDir . '/sources/Forms/IO/Format/StringIOFormat.php',
+ 'Combodo\\iTop\\Forms\\Register\\IORegister' => $baseDir . '/sources/Forms/Register/IORegister.php',
+ 'Combodo\\iTop\\Forms\\Register\\Option' => $baseDir . '/sources/Forms/Register/Option.php',
+ '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\\PropertyTree\\AbstractProperty' => $baseDir . '/sources/PropertyTree/AbstractProperty.php',
+ 'Combodo\\iTop\\PropertyTree\\Property' => $baseDir . '/sources/PropertyTree/Property.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTree' => $baseDir . '/sources/PropertyTree/PropertyTree.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTreeDesign' => $baseDir . '/sources/PropertyTree/PropertyTreeDesign.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTreeException' => $baseDir . '/sources/PropertyTree/PropertyTreeException.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTreeFactory' => $baseDir . '/sources/PropertyTree/PropertyTreeFactory.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\AbstractValueType' => $baseDir . '/sources/PropertyTree/ValueType/AbstractValueType.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeAggregateFunction' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeAggregateFunction.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeBoolean' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeBoolean.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoice' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeChoice.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoiceFromInput' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeChoiceFromInput.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClass' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClass.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttribute' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClassAttribute.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeGroupBy' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeGroupBy.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeValue' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeValue.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeCollection' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeCollection.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeFactory' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeFactory.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeIcon' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeIcon.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeInteger' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeInteger.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeLabel' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeLabel.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeOQL' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeOQL.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeProfileName' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeProfileName.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeString' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeString.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeText' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeText.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\\BsFormRenderer' => $baseDir . '/sources/Renderer/Bootstrap/BsFormRenderer.php',
@@ -497,6 +597,8 @@
'Combodo\\iTop\\Service\\Base\\ObjectRepository' => $baseDir . '/sources/Service/Base/ObjectRepository.php',
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => $baseDir . '/sources/Service/Base/iDataPostProcessor.php',
'Combodo\\iTop\\Service\\Cache\\DataModelDependantCache' => $baseDir . '/sources/Service/Cache/DataModelDependantCache.php',
+ 'Combodo\\iTop\\Service\\DependencyInjection\\DIException' => $baseDir . '/sources/Service/DependencyInjection/DIException.php',
+ 'Combodo\\iTop\\Service\\DependencyInjection\\DIService' => $baseDir . '/sources/Service/DependencyInjection/DIService.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => $baseDir . '/sources/Service/Events/Description/EventDataDescription.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => $baseDir . '/sources/Service/Events/Description/EventDescription.php',
'Combodo\\iTop\\Service\\Events\\EventData' => $baseDir . '/sources/Service/Events/EventData.php',
@@ -3071,6 +3173,227 @@
'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => $vendorDir . '/symfony/string/Slugger/AsciiSlugger.php',
'Symfony\\Component\\String\\Slugger\\SluggerInterface' => $vendorDir . '/symfony/string/Slugger/SluggerInterface.php',
'Symfony\\Component\\String\\UnicodeString' => $vendorDir . '/symfony/string/UnicodeString.php',
+ 'Symfony\\Component\\Validator\\Attribute\\HasNamedArguments' => $vendorDir . '/symfony/validator/Attribute/HasNamedArguments.php',
+ 'Symfony\\Component\\Validator\\Command\\DebugCommand' => $vendorDir . '/symfony/validator/Command/DebugCommand.php',
+ 'Symfony\\Component\\Validator\\Constraint' => $vendorDir . '/symfony/validator/Constraint.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidator' => $vendorDir . '/symfony/validator/ConstraintValidator.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidatorFactory' => $vendorDir . '/symfony/validator/ConstraintValidatorFactory.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidatorFactoryInterface' => $vendorDir . '/symfony/validator/ConstraintValidatorFactoryInterface.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidatorInterface' => $vendorDir . '/symfony/validator/ConstraintValidatorInterface.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolation' => $vendorDir . '/symfony/validator/ConstraintViolation.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolationInterface' => $vendorDir . '/symfony/validator/ConstraintViolationInterface.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolationList' => $vendorDir . '/symfony/validator/ConstraintViolationList.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolationListInterface' => $vendorDir . '/symfony/validator/ConstraintViolationListInterface.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AbstractComparison' => $vendorDir . '/symfony/validator/Constraints/AbstractComparison.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AbstractComparisonValidator' => $vendorDir . '/symfony/validator/Constraints/AbstractComparisonValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\All' => $vendorDir . '/symfony/validator/Constraints/All.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AllValidator' => $vendorDir . '/symfony/validator/Constraints/AllValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AtLeastOneOf' => $vendorDir . '/symfony/validator/Constraints/AtLeastOneOf.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AtLeastOneOfValidator' => $vendorDir . '/symfony/validator/Constraints/AtLeastOneOfValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Bic' => $vendorDir . '/symfony/validator/Constraints/Bic.php',
+ 'Symfony\\Component\\Validator\\Constraints\\BicValidator' => $vendorDir . '/symfony/validator/Constraints/BicValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Blank' => $vendorDir . '/symfony/validator/Constraints/Blank.php',
+ 'Symfony\\Component\\Validator\\Constraints\\BlankValidator' => $vendorDir . '/symfony/validator/Constraints/BlankValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Callback' => $vendorDir . '/symfony/validator/Constraints/Callback.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CallbackValidator' => $vendorDir . '/symfony/validator/Constraints/CallbackValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CardScheme' => $vendorDir . '/symfony/validator/Constraints/CardScheme.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CardSchemeValidator' => $vendorDir . '/symfony/validator/Constraints/CardSchemeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Cascade' => $vendorDir . '/symfony/validator/Constraints/Cascade.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Choice' => $vendorDir . '/symfony/validator/Constraints/Choice.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ChoiceValidator' => $vendorDir . '/symfony/validator/Constraints/ChoiceValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Cidr' => $vendorDir . '/symfony/validator/Constraints/Cidr.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CidrValidator' => $vendorDir . '/symfony/validator/Constraints/CidrValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Collection' => $vendorDir . '/symfony/validator/Constraints/Collection.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CollectionValidator' => $vendorDir . '/symfony/validator/Constraints/CollectionValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Composite' => $vendorDir . '/symfony/validator/Constraints/Composite.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Compound' => $vendorDir . '/symfony/validator/Constraints/Compound.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CompoundValidator' => $vendorDir . '/symfony/validator/Constraints/CompoundValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Count' => $vendorDir . '/symfony/validator/Constraints/Count.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CountValidator' => $vendorDir . '/symfony/validator/Constraints/CountValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Country' => $vendorDir . '/symfony/validator/Constraints/Country.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CountryValidator' => $vendorDir . '/symfony/validator/Constraints/CountryValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CssColor' => $vendorDir . '/symfony/validator/Constraints/CssColor.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CssColorValidator' => $vendorDir . '/symfony/validator/Constraints/CssColorValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Currency' => $vendorDir . '/symfony/validator/Constraints/Currency.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CurrencyValidator' => $vendorDir . '/symfony/validator/Constraints/CurrencyValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Date' => $vendorDir . '/symfony/validator/Constraints/Date.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DateTime' => $vendorDir . '/symfony/validator/Constraints/DateTime.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DateTimeValidator' => $vendorDir . '/symfony/validator/Constraints/DateTimeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DateValidator' => $vendorDir . '/symfony/validator/Constraints/DateValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DisableAutoMapping' => $vendorDir . '/symfony/validator/Constraints/DisableAutoMapping.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DivisibleBy' => $vendorDir . '/symfony/validator/Constraints/DivisibleBy.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DivisibleByValidator' => $vendorDir . '/symfony/validator/Constraints/DivisibleByValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Email' => $vendorDir . '/symfony/validator/Constraints/Email.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EmailValidator' => $vendorDir . '/symfony/validator/Constraints/EmailValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EnableAutoMapping' => $vendorDir . '/symfony/validator/Constraints/EnableAutoMapping.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EqualTo' => $vendorDir . '/symfony/validator/Constraints/EqualTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EqualToValidator' => $vendorDir . '/symfony/validator/Constraints/EqualToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Existence' => $vendorDir . '/symfony/validator/Constraints/Existence.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Expression' => $vendorDir . '/symfony/validator/Constraints/Expression.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageProvider' => $vendorDir . '/symfony/validator/Constraints/ExpressionLanguageProvider.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageSyntax' => $vendorDir . '/symfony/validator/Constraints/ExpressionLanguageSyntax.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageSyntaxValidator' => $vendorDir . '/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionSyntax' => $vendorDir . '/symfony/validator/Constraints/ExpressionSyntax.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionSyntaxValidator' => $vendorDir . '/symfony/validator/Constraints/ExpressionSyntaxValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionValidator' => $vendorDir . '/symfony/validator/Constraints/ExpressionValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\File' => $vendorDir . '/symfony/validator/Constraints/File.php',
+ 'Symfony\\Component\\Validator\\Constraints\\FileValidator' => $vendorDir . '/symfony/validator/Constraints/FileValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThan' => $vendorDir . '/symfony/validator/Constraints/GreaterThan.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqual' => $vendorDir . '/symfony/validator/Constraints/GreaterThanOrEqual.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqualValidator' => $vendorDir . '/symfony/validator/Constraints/GreaterThanOrEqualValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThanValidator' => $vendorDir . '/symfony/validator/Constraints/GreaterThanValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GroupSequence' => $vendorDir . '/symfony/validator/Constraints/GroupSequence.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GroupSequenceProvider' => $vendorDir . '/symfony/validator/Constraints/GroupSequenceProvider.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Hostname' => $vendorDir . '/symfony/validator/Constraints/Hostname.php',
+ 'Symfony\\Component\\Validator\\Constraints\\HostnameValidator' => $vendorDir . '/symfony/validator/Constraints/HostnameValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Iban' => $vendorDir . '/symfony/validator/Constraints/Iban.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IbanValidator' => $vendorDir . '/symfony/validator/Constraints/IbanValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IdenticalTo' => $vendorDir . '/symfony/validator/Constraints/IdenticalTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IdenticalToValidator' => $vendorDir . '/symfony/validator/Constraints/IdenticalToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Image' => $vendorDir . '/symfony/validator/Constraints/Image.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ImageValidator' => $vendorDir . '/symfony/validator/Constraints/ImageValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Ip' => $vendorDir . '/symfony/validator/Constraints/Ip.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IpValidator' => $vendorDir . '/symfony/validator/Constraints/IpValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsFalse' => $vendorDir . '/symfony/validator/Constraints/IsFalse.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsFalseValidator' => $vendorDir . '/symfony/validator/Constraints/IsFalseValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsNull' => $vendorDir . '/symfony/validator/Constraints/IsNull.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsNullValidator' => $vendorDir . '/symfony/validator/Constraints/IsNullValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsTrue' => $vendorDir . '/symfony/validator/Constraints/IsTrue.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsTrueValidator' => $vendorDir . '/symfony/validator/Constraints/IsTrueValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Isbn' => $vendorDir . '/symfony/validator/Constraints/Isbn.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsbnValidator' => $vendorDir . '/symfony/validator/Constraints/IsbnValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Isin' => $vendorDir . '/symfony/validator/Constraints/Isin.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsinValidator' => $vendorDir . '/symfony/validator/Constraints/IsinValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Issn' => $vendorDir . '/symfony/validator/Constraints/Issn.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IssnValidator' => $vendorDir . '/symfony/validator/Constraints/IssnValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Json' => $vendorDir . '/symfony/validator/Constraints/Json.php',
+ 'Symfony\\Component\\Validator\\Constraints\\JsonValidator' => $vendorDir . '/symfony/validator/Constraints/JsonValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Language' => $vendorDir . '/symfony/validator/Constraints/Language.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LanguageValidator' => $vendorDir . '/symfony/validator/Constraints/LanguageValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Length' => $vendorDir . '/symfony/validator/Constraints/Length.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LengthValidator' => $vendorDir . '/symfony/validator/Constraints/LengthValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThan' => $vendorDir . '/symfony/validator/Constraints/LessThan.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqual' => $vendorDir . '/symfony/validator/Constraints/LessThanOrEqual.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqualValidator' => $vendorDir . '/symfony/validator/Constraints/LessThanOrEqualValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThanValidator' => $vendorDir . '/symfony/validator/Constraints/LessThanValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Locale' => $vendorDir . '/symfony/validator/Constraints/Locale.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LocaleValidator' => $vendorDir . '/symfony/validator/Constraints/LocaleValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Luhn' => $vendorDir . '/symfony/validator/Constraints/Luhn.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LuhnValidator' => $vendorDir . '/symfony/validator/Constraints/LuhnValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Negative' => $vendorDir . '/symfony/validator/Constraints/Negative.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NegativeOrZero' => $vendorDir . '/symfony/validator/Constraints/NegativeOrZero.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NoSuspiciousCharacters' => $vendorDir . '/symfony/validator/Constraints/NoSuspiciousCharacters.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NoSuspiciousCharactersValidator' => $vendorDir . '/symfony/validator/Constraints/NoSuspiciousCharactersValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotBlank' => $vendorDir . '/symfony/validator/Constraints/NotBlank.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotBlankValidator' => $vendorDir . '/symfony/validator/Constraints/NotBlankValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotCompromisedPassword' => $vendorDir . '/symfony/validator/Constraints/NotCompromisedPassword.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotCompromisedPasswordValidator' => $vendorDir . '/symfony/validator/Constraints/NotCompromisedPasswordValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotEqualTo' => $vendorDir . '/symfony/validator/Constraints/NotEqualTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotEqualToValidator' => $vendorDir . '/symfony/validator/Constraints/NotEqualToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalTo' => $vendorDir . '/symfony/validator/Constraints/NotIdenticalTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalToValidator' => $vendorDir . '/symfony/validator/Constraints/NotIdenticalToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotNull' => $vendorDir . '/symfony/validator/Constraints/NotNull.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotNullValidator' => $vendorDir . '/symfony/validator/Constraints/NotNullValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Optional' => $vendorDir . '/symfony/validator/Constraints/Optional.php',
+ 'Symfony\\Component\\Validator\\Constraints\\PasswordStrength' => $vendorDir . '/symfony/validator/Constraints/PasswordStrength.php',
+ 'Symfony\\Component\\Validator\\Constraints\\PasswordStrengthValidator' => $vendorDir . '/symfony/validator/Constraints/PasswordStrengthValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Positive' => $vendorDir . '/symfony/validator/Constraints/Positive.php',
+ 'Symfony\\Component\\Validator\\Constraints\\PositiveOrZero' => $vendorDir . '/symfony/validator/Constraints/PositiveOrZero.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Range' => $vendorDir . '/symfony/validator/Constraints/Range.php',
+ 'Symfony\\Component\\Validator\\Constraints\\RangeValidator' => $vendorDir . '/symfony/validator/Constraints/RangeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Regex' => $vendorDir . '/symfony/validator/Constraints/Regex.php',
+ 'Symfony\\Component\\Validator\\Constraints\\RegexValidator' => $vendorDir . '/symfony/validator/Constraints/RegexValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Required' => $vendorDir . '/symfony/validator/Constraints/Required.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Sequentially' => $vendorDir . '/symfony/validator/Constraints/Sequentially.php',
+ 'Symfony\\Component\\Validator\\Constraints\\SequentiallyValidator' => $vendorDir . '/symfony/validator/Constraints/SequentiallyValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Time' => $vendorDir . '/symfony/validator/Constraints/Time.php',
+ 'Symfony\\Component\\Validator\\Constraints\\TimeValidator' => $vendorDir . '/symfony/validator/Constraints/TimeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Timezone' => $vendorDir . '/symfony/validator/Constraints/Timezone.php',
+ 'Symfony\\Component\\Validator\\Constraints\\TimezoneValidator' => $vendorDir . '/symfony/validator/Constraints/TimezoneValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Traverse' => $vendorDir . '/symfony/validator/Constraints/Traverse.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Type' => $vendorDir . '/symfony/validator/Constraints/Type.php',
+ 'Symfony\\Component\\Validator\\Constraints\\TypeValidator' => $vendorDir . '/symfony/validator/Constraints/TypeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Ulid' => $vendorDir . '/symfony/validator/Constraints/Ulid.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UlidValidator' => $vendorDir . '/symfony/validator/Constraints/UlidValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Unique' => $vendorDir . '/symfony/validator/Constraints/Unique.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UniqueValidator' => $vendorDir . '/symfony/validator/Constraints/UniqueValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Url' => $vendorDir . '/symfony/validator/Constraints/Url.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UrlValidator' => $vendorDir . '/symfony/validator/Constraints/UrlValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Uuid' => $vendorDir . '/symfony/validator/Constraints/Uuid.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UuidValidator' => $vendorDir . '/symfony/validator/Constraints/UuidValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Valid' => $vendorDir . '/symfony/validator/Constraints/Valid.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ValidValidator' => $vendorDir . '/symfony/validator/Constraints/ValidValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\When' => $vendorDir . '/symfony/validator/Constraints/When.php',
+ 'Symfony\\Component\\Validator\\Constraints\\WhenValidator' => $vendorDir . '/symfony/validator/Constraints/WhenValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ZeroComparisonConstraintTrait' => $vendorDir . '/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php',
+ 'Symfony\\Component\\Validator\\ContainerConstraintValidatorFactory' => $vendorDir . '/symfony/validator/ContainerConstraintValidatorFactory.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContext' => $vendorDir . '/symfony/validator/Context/ExecutionContext.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactory' => $vendorDir . '/symfony/validator/Context/ExecutionContextFactory.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactoryInterface' => $vendorDir . '/symfony/validator/Context/ExecutionContextFactoryInterface.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContextInterface' => $vendorDir . '/symfony/validator/Context/ExecutionContextInterface.php',
+ 'Symfony\\Component\\Validator\\DataCollector\\ValidatorDataCollector' => $vendorDir . '/symfony/validator/DataCollector/ValidatorDataCollector.php',
+ 'Symfony\\Component\\Validator\\DependencyInjection\\AddAutoMappingConfigurationPass' => $vendorDir . '/symfony/validator/DependencyInjection/AddAutoMappingConfigurationPass.php',
+ 'Symfony\\Component\\Validator\\DependencyInjection\\AddConstraintValidatorsPass' => $vendorDir . '/symfony/validator/DependencyInjection/AddConstraintValidatorsPass.php',
+ 'Symfony\\Component\\Validator\\DependencyInjection\\AddValidatorInitializersPass' => $vendorDir . '/symfony/validator/DependencyInjection/AddValidatorInitializersPass.php',
+ 'Symfony\\Component\\Validator\\Exception\\BadMethodCallException' => $vendorDir . '/symfony/validator/Exception/BadMethodCallException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ConstraintDefinitionException' => $vendorDir . '/symfony/validator/Exception/ConstraintDefinitionException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/validator/Exception/ExceptionInterface.php',
+ 'Symfony\\Component\\Validator\\Exception\\GroupDefinitionException' => $vendorDir . '/symfony/validator/Exception/GroupDefinitionException.php',
+ 'Symfony\\Component\\Validator\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/validator/Exception/InvalidArgumentException.php',
+ 'Symfony\\Component\\Validator\\Exception\\InvalidOptionsException' => $vendorDir . '/symfony/validator/Exception/InvalidOptionsException.php',
+ 'Symfony\\Component\\Validator\\Exception\\LogicException' => $vendorDir . '/symfony/validator/Exception/LogicException.php',
+ 'Symfony\\Component\\Validator\\Exception\\MappingException' => $vendorDir . '/symfony/validator/Exception/MappingException.php',
+ 'Symfony\\Component\\Validator\\Exception\\MissingOptionsException' => $vendorDir . '/symfony/validator/Exception/MissingOptionsException.php',
+ 'Symfony\\Component\\Validator\\Exception\\NoSuchMetadataException' => $vendorDir . '/symfony/validator/Exception/NoSuchMetadataException.php',
+ 'Symfony\\Component\\Validator\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/validator/Exception/OutOfBoundsException.php',
+ 'Symfony\\Component\\Validator\\Exception\\RuntimeException' => $vendorDir . '/symfony/validator/Exception/RuntimeException.php',
+ 'Symfony\\Component\\Validator\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/validator/Exception/UnexpectedTypeException.php',
+ 'Symfony\\Component\\Validator\\Exception\\UnexpectedValueException' => $vendorDir . '/symfony/validator/Exception/UnexpectedValueException.php',
+ 'Symfony\\Component\\Validator\\Exception\\UnsupportedMetadataException' => $vendorDir . '/symfony/validator/Exception/UnsupportedMetadataException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ValidationFailedException' => $vendorDir . '/symfony/validator/Exception/ValidationFailedException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ValidatorException' => $vendorDir . '/symfony/validator/Exception/ValidatorException.php',
+ 'Symfony\\Component\\Validator\\GroupProviderInterface' => $vendorDir . '/symfony/validator/GroupProviderInterface.php',
+ 'Symfony\\Component\\Validator\\GroupSequenceProviderInterface' => $vendorDir . '/symfony/validator/GroupSequenceProviderInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\AutoMappingStrategy' => $vendorDir . '/symfony/validator/Mapping/AutoMappingStrategy.php',
+ 'Symfony\\Component\\Validator\\Mapping\\CascadingStrategy' => $vendorDir . '/symfony/validator/Mapping/CascadingStrategy.php',
+ 'Symfony\\Component\\Validator\\Mapping\\ClassMetadata' => $vendorDir . '/symfony/validator/Mapping/ClassMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\ClassMetadataInterface' => $vendorDir . '/symfony/validator/Mapping/ClassMetadataInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Factory\\BlackHoleMetadataFactory' => $vendorDir . '/symfony/validator/Mapping/Factory/BlackHoleMetadataFactory.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Factory\\LazyLoadingMetadataFactory' => $vendorDir . '/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Factory\\MetadataFactoryInterface' => $vendorDir . '/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\GenericMetadata' => $vendorDir . '/symfony/validator/Mapping/GenericMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\GetterMetadata' => $vendorDir . '/symfony/validator/Mapping/GetterMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AbstractLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/AbstractLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AnnotationLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/AnnotationLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AttributeLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/AttributeLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AutoMappingTrait' => $vendorDir . '/symfony/validator/Mapping/Loader/AutoMappingTrait.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\FileLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/FileLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\FilesLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/FilesLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderChain' => $vendorDir . '/symfony/validator/Mapping/Loader/LoaderChain.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderInterface' => $vendorDir . '/symfony/validator/Mapping/Loader/LoaderInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\PropertyInfoLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/PropertyInfoLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\StaticMethodLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/StaticMethodLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/XmlFileLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFilesLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/XmlFilesLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/YamlFileLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFilesLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/YamlFilesLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\MemberMetadata' => $vendorDir . '/symfony/validator/Mapping/MemberMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\MetadataInterface' => $vendorDir . '/symfony/validator/Mapping/MetadataInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\PropertyMetadata' => $vendorDir . '/symfony/validator/Mapping/PropertyMetadata.php',
+ '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\\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',
+ 'Symfony\\Component\\Validator\\Validator\\ContextualValidatorInterface' => $vendorDir . '/symfony/validator/Validator/ContextualValidatorInterface.php',
+ 'Symfony\\Component\\Validator\\Validator\\LazyProperty' => $vendorDir . '/symfony/validator/Validator/LazyProperty.php',
+ 'Symfony\\Component\\Validator\\Validator\\RecursiveContextualValidator' => $vendorDir . '/symfony/validator/Validator/RecursiveContextualValidator.php',
+ 'Symfony\\Component\\Validator\\Validator\\RecursiveValidator' => $vendorDir . '/symfony/validator/Validator/RecursiveValidator.php',
+ 'Symfony\\Component\\Validator\\Validator\\TraceableValidator' => $vendorDir . '/symfony/validator/Validator/TraceableValidator.php',
+ 'Symfony\\Component\\Validator\\Validator\\ValidatorInterface' => $vendorDir . '/symfony/validator/Validator/ValidatorInterface.php',
+ 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilder' => $vendorDir . '/symfony/validator/Violation/ConstraintViolationBuilder.php',
+ 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilderInterface' => $vendorDir . '/symfony/validator/Violation/ConstraintViolationBuilderInterface.php',
'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => $vendorDir . '/symfony/var-dumper/Caster/AmqpCaster.php',
'Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => $vendorDir . '/symfony/var-dumper/Caster/ArgsStub.php',
'Symfony\\Component\\VarDumper\\Caster\\Caster' => $vendorDir . '/symfony/var-dumper/Caster/Caster.php',
diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php
index 602597214b..15036b1a53 100644
--- a/lib/composer/autoload_psr4.php
+++ b/lib/composer/autoload_psr4.php
@@ -22,6 +22,7 @@
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
+ 'Symfony\\Component\\Validator\\' => array($vendorDir . '/symfony/validator'),
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'Symfony\\Component\\Stopwatch\\' => array($vendorDir . '/symfony/stopwatch'),
'Symfony\\Component\\Security\\Csrf\\' => array($vendorDir . '/symfony/security-csrf'),
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index 7c85ebdb58..ab4134cc7a 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -49,6 +49,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\VarExporter\\' => 30,
'Symfony\\Component\\VarDumper\\' => 28,
+ 'Symfony\\Component\\Validator\\' => 28,
'Symfony\\Component\\String\\' => 25,
'Symfony\\Component\\Stopwatch\\' => 28,
'Symfony\\Component\\Security\\Csrf\\' => 32,
@@ -181,6 +182,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
array (
0 => __DIR__ . '/..' . '/symfony/var-dumper',
),
+ 'Symfony\\Component\\Validator\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/validator',
+ ),
'Symfony\\Component\\String\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/string',
@@ -625,6 +630,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Toolbar\\ToolbarSpacer\\ToolbarSpacer' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Toolbar/ToolbarSpacer/ToolbarSpacer.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Toolbar\\ToolbarSpacer\\ToolbarSpacerUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Toolbar/ToolbarSpacer/ToolbarSpacerUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Toolbar\\ToolbarUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Toolbar/ToolbarUIBlockFactory.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboForm\\TurboForm' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/TurboForm/TurboForm.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboForm\\TurboFormUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/TurboForm/TurboFormUIBlockFactory.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboUpdate\\TurboStream' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/TurboStream/TurboStream.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Component\\TurboUpdate\\TurboStreamUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/TurboStream/TurboStreamUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntry' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/ActivityEntry.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntryFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/ActivityEntryFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\CMDBChangeOp\\CMDBChangeOpAttachmentAddedFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/CMDBChangeOp/CMDBChangeOpAttachmentAddedFactory.php',
@@ -722,6 +731,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ActivityPanelController.php',
'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ObjectController.php',
+ 'Combodo\\iTop\\Controller\\Base\\Layout\\OqlController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/OqlController.php',
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => __DIR__ . '/../..' . '/sources/Controller/Links/LinkSetController.php',
'Combodo\\iTop\\Controller\\Newsroom\\iTopNewsroomController' => __DIR__ . '/../..' . '/sources/Controller/Newsroom/iTopNewsroomController.php',
'Combodo\\iTop\\Controller\\Notifications\\ActionController' => __DIR__ . '/../..' . '/sources/Controller/Notifications/ActionController.php',
@@ -854,8 +864,103 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'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\\Forms\\Block\\AbstractFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/AbstractFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\AbstractTypeFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/AbstractTypeFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\CheckboxFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/CheckboxFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/ChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\ChoiceFromInputsBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/ChoiceFromInputsBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\CollectionBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/CollectionBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\DateFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/DateFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\DateTimeFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/DateTimeFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\FormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/FormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\HiddenFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/HiddenFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\IntegerFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/IntegerFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\NumberFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/NumberFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\TextAreaFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextAreaFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Base\\TextFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Base/TextFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeTypeChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeTypeChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\AttributeValueChoiceFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\Dashlet\\AggregateFunctionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/Dashlet/AggregateFunctionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\Dashlet\\ClassAttributeGroupByFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/Dashlet/ClassAttributeGroupByFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\LabelFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/LabelFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\DataModel\\OqlFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/DataModel/OqlFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\AbstractExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/AbstractExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\BooleanExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/BooleanExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\NumberExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/NumberExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\Expression\\StringExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/StringExpressionFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Block\\FormBlockException' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockException.php',
+ 'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockHelper.php',
+ 'Combodo\\iTop\\Forms\\Block\\FormBlockService' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockService.php',
+ 'Combodo\\iTop\\Forms\\Block\\IFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/IFormBlock.php',
+ 'Combodo\\iTop\\Forms\\Compiler\\FormsCompiler' => __DIR__ . '/../..' . '/sources/Forms/Compiler/FormsCompiler.php',
+ 'Combodo\\iTop\\Forms\\Compiler\\FormsCompilerException' => __DIR__ . '/../..' . '/sources/Forms/Compiler/FormsCompilerException.php',
+ 'Combodo\\iTop\\Forms\\Controller\\FormsController' => __DIR__ . '/../..' . '/sources/Forms/Controller/FormsController.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyHandler.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyMap.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormBuilder.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilderException' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormBuilderException.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormHelper' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormHelper.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\FormTypeExtension' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormTypeExtension.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormType' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/ResolvedFormType.php',
+ 'Combodo\\iTop\\Forms\\FormBuilder\\ResolvedFormTypeFactory' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/ResolvedFormTypeFactory.php',
+ 'Combodo\\iTop\\Forms\\FormType\\Base\\ChoiceFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/Base/ChoiceFormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\Base\\CollectionFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/Base/CollectionFormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\Base\\FormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/Base/FormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\DataModel\\OqlFormType' => __DIR__ . '/../..' . '/sources/Forms/FormType/DataModel/OqlFormType.php',
+ 'Combodo\\iTop\\Forms\\FormType\\FormTypeHelper' => __DIR__ . '/../..' . '/sources/Forms/FormType/FormTypeHelper.php',
+ 'Combodo\\iTop\\Forms\\Forms' => __DIR__ . '/../..' . '/sources/Forms/Forms.php',
+ 'Combodo\\iTop\\Forms\\FormsException' => __DIR__ . '/../..' . '/sources/Forms/FormsException.php',
+ 'Combodo\\iTop\\Forms\\IO\\AbstractFormIO' => __DIR__ . '/../..' . '/sources/Forms/IO/AbstractFormIO.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\AbstractConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/AbstractConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\ChoiceValueToLabelConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/ChoiceValueToLabelConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\CollectionToCountConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/CollectionToCountConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\Converter\\OqlToClassConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/OqlToClassConverter.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormBinding' => __DIR__ . '/../..' . '/sources/Forms/IO/FormBinding.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormBlockIOException' => __DIR__ . '/../..' . '/sources/Forms/IO/FormBlockIOException.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormInput' => __DIR__ . '/../..' . '/sources/Forms/IO/FormInput.php',
+ 'Combodo\\iTop\\Forms\\IO\\FormOutput' => __DIR__ . '/../..' . '/sources/Forms/IO/FormOutput.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AbstractIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/AbstractIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AttributeIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/AttributeIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AttributeTypeArrayIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/AttributeTypeArrayIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\AttributeTypeIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/AttributeTypeIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\BooleanIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/BooleanIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\ClassIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/ClassIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\IntegerIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/IntegerIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\NumberIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/NumberIOFormat.php',
+ 'Combodo\\iTop\\Forms\\IO\\Format\\StringIOFormat' => __DIR__ . '/../..' . '/sources/Forms/IO/Format/StringIOFormat.php',
+ 'Combodo\\iTop\\Forms\\Register\\IORegister' => __DIR__ . '/../..' . '/sources/Forms/Register/IORegister.php',
+ 'Combodo\\iTop\\Forms\\Register\\Option' => __DIR__ . '/../..' . '/sources/Forms/Register/Option.php',
+ '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\\PropertyTree\\AbstractProperty' => __DIR__ . '/../..' . '/sources/PropertyTree/AbstractProperty.php',
+ 'Combodo\\iTop\\PropertyTree\\Property' => __DIR__ . '/../..' . '/sources/PropertyTree/Property.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTree' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTree.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTreeDesign' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeDesign.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTreeException' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeException.php',
+ 'Combodo\\iTop\\PropertyTree\\PropertyTreeFactory' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeFactory.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\AbstractValueType' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/AbstractValueType.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeAggregateFunction' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeAggregateFunction.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeBoolean' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeBoolean.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoice' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeChoice.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoiceFromInput' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeChoiceFromInput.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClass' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClass.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttribute' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClassAttribute.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeGroupBy' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeGroupBy.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeValue' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeValue.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeCollection' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeCollection.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeFactory' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeFactory.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeIcon' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeIcon.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeInteger' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeInteger.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeLabel' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeLabel.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeOQL' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeOQL.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeProfileName' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeProfileName.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeString' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeString.php',
+ 'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeText' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeText.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\\BsFormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFormRenderer.php',
@@ -878,6 +983,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\Base\\ObjectRepository' => __DIR__ . '/../..' . '/sources/Service/Base/ObjectRepository.php',
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Base/iDataPostProcessor.php',
'Combodo\\iTop\\Service\\Cache\\DataModelDependantCache' => __DIR__ . '/../..' . '/sources/Service/Cache/DataModelDependantCache.php',
+ 'Combodo\\iTop\\Service\\DependencyInjection\\DIException' => __DIR__ . '/../..' . '/sources/Service/DependencyInjection/DIException.php',
+ 'Combodo\\iTop\\Service\\DependencyInjection\\DIService' => __DIR__ . '/../..' . '/sources/Service/DependencyInjection/DIService.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => __DIR__ . '/../..' . '/sources/Service/Events/Description/EventDataDescription.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => __DIR__ . '/../..' . '/sources/Service/Events/Description/EventDescription.php',
'Combodo\\iTop\\Service\\Events\\EventData' => __DIR__ . '/../..' . '/sources/Service/Events/EventData.php',
@@ -3452,6 +3559,227 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => __DIR__ . '/..' . '/symfony/string/Slugger/AsciiSlugger.php',
'Symfony\\Component\\String\\Slugger\\SluggerInterface' => __DIR__ . '/..' . '/symfony/string/Slugger/SluggerInterface.php',
'Symfony\\Component\\String\\UnicodeString' => __DIR__ . '/..' . '/symfony/string/UnicodeString.php',
+ 'Symfony\\Component\\Validator\\Attribute\\HasNamedArguments' => __DIR__ . '/..' . '/symfony/validator/Attribute/HasNamedArguments.php',
+ 'Symfony\\Component\\Validator\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/validator/Command/DebugCommand.php',
+ 'Symfony\\Component\\Validator\\Constraint' => __DIR__ . '/..' . '/symfony/validator/Constraint.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidator' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidator.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidatorFactory' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidatorFactory.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidatorFactoryInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidatorFactoryInterface.php',
+ 'Symfony\\Component\\Validator\\ConstraintValidatorInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidatorInterface.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolation' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolation.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolationInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolationInterface.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolationList' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolationList.php',
+ 'Symfony\\Component\\Validator\\ConstraintViolationListInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolationListInterface.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AbstractComparison' => __DIR__ . '/..' . '/symfony/validator/Constraints/AbstractComparison.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AbstractComparisonValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/AbstractComparisonValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\All' => __DIR__ . '/..' . '/symfony/validator/Constraints/All.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AllValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/AllValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AtLeastOneOf' => __DIR__ . '/..' . '/symfony/validator/Constraints/AtLeastOneOf.php',
+ 'Symfony\\Component\\Validator\\Constraints\\AtLeastOneOfValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/AtLeastOneOfValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Bic' => __DIR__ . '/..' . '/symfony/validator/Constraints/Bic.php',
+ 'Symfony\\Component\\Validator\\Constraints\\BicValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/BicValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Blank' => __DIR__ . '/..' . '/symfony/validator/Constraints/Blank.php',
+ 'Symfony\\Component\\Validator\\Constraints\\BlankValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/BlankValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Callback' => __DIR__ . '/..' . '/symfony/validator/Constraints/Callback.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CallbackValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CallbackValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CardScheme' => __DIR__ . '/..' . '/symfony/validator/Constraints/CardScheme.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CardSchemeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CardSchemeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Cascade' => __DIR__ . '/..' . '/symfony/validator/Constraints/Cascade.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Choice' => __DIR__ . '/..' . '/symfony/validator/Constraints/Choice.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ChoiceValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ChoiceValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Cidr' => __DIR__ . '/..' . '/symfony/validator/Constraints/Cidr.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CidrValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CidrValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Collection' => __DIR__ . '/..' . '/symfony/validator/Constraints/Collection.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CollectionValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CollectionValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Composite' => __DIR__ . '/..' . '/symfony/validator/Constraints/Composite.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Compound' => __DIR__ . '/..' . '/symfony/validator/Constraints/Compound.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CompoundValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CompoundValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Count' => __DIR__ . '/..' . '/symfony/validator/Constraints/Count.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CountValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CountValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Country' => __DIR__ . '/..' . '/symfony/validator/Constraints/Country.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CountryValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CountryValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CssColor' => __DIR__ . '/..' . '/symfony/validator/Constraints/CssColor.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CssColorValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CssColorValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Currency' => __DIR__ . '/..' . '/symfony/validator/Constraints/Currency.php',
+ 'Symfony\\Component\\Validator\\Constraints\\CurrencyValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CurrencyValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Date' => __DIR__ . '/..' . '/symfony/validator/Constraints/Date.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DateTime' => __DIR__ . '/..' . '/symfony/validator/Constraints/DateTime.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DateTimeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/DateTimeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DateValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/DateValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DisableAutoMapping' => __DIR__ . '/..' . '/symfony/validator/Constraints/DisableAutoMapping.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DivisibleBy' => __DIR__ . '/..' . '/symfony/validator/Constraints/DivisibleBy.php',
+ 'Symfony\\Component\\Validator\\Constraints\\DivisibleByValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/DivisibleByValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Email' => __DIR__ . '/..' . '/symfony/validator/Constraints/Email.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EmailValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/EmailValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EnableAutoMapping' => __DIR__ . '/..' . '/symfony/validator/Constraints/EnableAutoMapping.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EqualTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/EqualTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\EqualToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/EqualToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Existence' => __DIR__ . '/..' . '/symfony/validator/Constraints/Existence.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Expression' => __DIR__ . '/..' . '/symfony/validator/Constraints/Expression.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageProvider' => __DIR__ . '/..' . '/symfony/validator/Constraints/ExpressionLanguageProvider.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageSyntax' => __DIR__ . '/..' . '/symfony/validator/Constraints/ExpressionLanguageSyntax.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageSyntaxValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionSyntax' => __DIR__ . '/..' . '/symfony/validator/Constraints/ExpressionSyntax.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionSyntaxValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ExpressionSyntaxValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ExpressionValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ExpressionValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\File' => __DIR__ . '/..' . '/symfony/validator/Constraints/File.php',
+ 'Symfony\\Component\\Validator\\Constraints\\FileValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/FileValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThan' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThan.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqual' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThanOrEqual.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqualValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThanOrEqualValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GreaterThanValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThanValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GroupSequence' => __DIR__ . '/..' . '/symfony/validator/Constraints/GroupSequence.php',
+ 'Symfony\\Component\\Validator\\Constraints\\GroupSequenceProvider' => __DIR__ . '/..' . '/symfony/validator/Constraints/GroupSequenceProvider.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Hostname' => __DIR__ . '/..' . '/symfony/validator/Constraints/Hostname.php',
+ 'Symfony\\Component\\Validator\\Constraints\\HostnameValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/HostnameValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Iban' => __DIR__ . '/..' . '/symfony/validator/Constraints/Iban.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IbanValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IbanValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IdenticalTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/IdenticalTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IdenticalToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IdenticalToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Image' => __DIR__ . '/..' . '/symfony/validator/Constraints/Image.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ImageValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ImageValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Ip' => __DIR__ . '/..' . '/symfony/validator/Constraints/Ip.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IpValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IpValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsFalse' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsFalse.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsFalseValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsFalseValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsNull' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsNull.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsNullValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsNullValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsTrue' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsTrue.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsTrueValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsTrueValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Isbn' => __DIR__ . '/..' . '/symfony/validator/Constraints/Isbn.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsbnValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsbnValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Isin' => __DIR__ . '/..' . '/symfony/validator/Constraints/Isin.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IsinValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsinValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Issn' => __DIR__ . '/..' . '/symfony/validator/Constraints/Issn.php',
+ 'Symfony\\Component\\Validator\\Constraints\\IssnValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IssnValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Json' => __DIR__ . '/..' . '/symfony/validator/Constraints/Json.php',
+ 'Symfony\\Component\\Validator\\Constraints\\JsonValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/JsonValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Language' => __DIR__ . '/..' . '/symfony/validator/Constraints/Language.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LanguageValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LanguageValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Length' => __DIR__ . '/..' . '/symfony/validator/Constraints/Length.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LengthValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LengthValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThan' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThan.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqual' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThanOrEqual.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqualValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThanOrEqualValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LessThanValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThanValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Locale' => __DIR__ . '/..' . '/symfony/validator/Constraints/Locale.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LocaleValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LocaleValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Luhn' => __DIR__ . '/..' . '/symfony/validator/Constraints/Luhn.php',
+ 'Symfony\\Component\\Validator\\Constraints\\LuhnValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LuhnValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Negative' => __DIR__ . '/..' . '/symfony/validator/Constraints/Negative.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NegativeOrZero' => __DIR__ . '/..' . '/symfony/validator/Constraints/NegativeOrZero.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NoSuspiciousCharacters' => __DIR__ . '/..' . '/symfony/validator/Constraints/NoSuspiciousCharacters.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NoSuspiciousCharactersValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NoSuspiciousCharactersValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotBlank' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotBlank.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotBlankValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotBlankValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotCompromisedPassword' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotCompromisedPassword.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotCompromisedPasswordValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotCompromisedPasswordValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotEqualTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotEqualTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotEqualToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotEqualToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotIdenticalTo.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotIdenticalToValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotNull' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotNull.php',
+ 'Symfony\\Component\\Validator\\Constraints\\NotNullValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotNullValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Optional' => __DIR__ . '/..' . '/symfony/validator/Constraints/Optional.php',
+ 'Symfony\\Component\\Validator\\Constraints\\PasswordStrength' => __DIR__ . '/..' . '/symfony/validator/Constraints/PasswordStrength.php',
+ 'Symfony\\Component\\Validator\\Constraints\\PasswordStrengthValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/PasswordStrengthValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Positive' => __DIR__ . '/..' . '/symfony/validator/Constraints/Positive.php',
+ 'Symfony\\Component\\Validator\\Constraints\\PositiveOrZero' => __DIR__ . '/..' . '/symfony/validator/Constraints/PositiveOrZero.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Range' => __DIR__ . '/..' . '/symfony/validator/Constraints/Range.php',
+ 'Symfony\\Component\\Validator\\Constraints\\RangeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/RangeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Regex' => __DIR__ . '/..' . '/symfony/validator/Constraints/Regex.php',
+ 'Symfony\\Component\\Validator\\Constraints\\RegexValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/RegexValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Required' => __DIR__ . '/..' . '/symfony/validator/Constraints/Required.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Sequentially' => __DIR__ . '/..' . '/symfony/validator/Constraints/Sequentially.php',
+ 'Symfony\\Component\\Validator\\Constraints\\SequentiallyValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/SequentiallyValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Time' => __DIR__ . '/..' . '/symfony/validator/Constraints/Time.php',
+ 'Symfony\\Component\\Validator\\Constraints\\TimeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/TimeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Timezone' => __DIR__ . '/..' . '/symfony/validator/Constraints/Timezone.php',
+ 'Symfony\\Component\\Validator\\Constraints\\TimezoneValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/TimezoneValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Traverse' => __DIR__ . '/..' . '/symfony/validator/Constraints/Traverse.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Type' => __DIR__ . '/..' . '/symfony/validator/Constraints/Type.php',
+ 'Symfony\\Component\\Validator\\Constraints\\TypeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/TypeValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Ulid' => __DIR__ . '/..' . '/symfony/validator/Constraints/Ulid.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UlidValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/UlidValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Unique' => __DIR__ . '/..' . '/symfony/validator/Constraints/Unique.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UniqueValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/UniqueValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Url' => __DIR__ . '/..' . '/symfony/validator/Constraints/Url.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UrlValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/UrlValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Uuid' => __DIR__ . '/..' . '/symfony/validator/Constraints/Uuid.php',
+ 'Symfony\\Component\\Validator\\Constraints\\UuidValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/UuidValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\Valid' => __DIR__ . '/..' . '/symfony/validator/Constraints/Valid.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ValidValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ValidValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\When' => __DIR__ . '/..' . '/symfony/validator/Constraints/When.php',
+ 'Symfony\\Component\\Validator\\Constraints\\WhenValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/WhenValidator.php',
+ 'Symfony\\Component\\Validator\\Constraints\\ZeroComparisonConstraintTrait' => __DIR__ . '/..' . '/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php',
+ 'Symfony\\Component\\Validator\\ContainerConstraintValidatorFactory' => __DIR__ . '/..' . '/symfony/validator/ContainerConstraintValidatorFactory.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContext' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContext.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactory' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContextFactory.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactoryInterface' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContextFactoryInterface.php',
+ 'Symfony\\Component\\Validator\\Context\\ExecutionContextInterface' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContextInterface.php',
+ 'Symfony\\Component\\Validator\\DataCollector\\ValidatorDataCollector' => __DIR__ . '/..' . '/symfony/validator/DataCollector/ValidatorDataCollector.php',
+ 'Symfony\\Component\\Validator\\DependencyInjection\\AddAutoMappingConfigurationPass' => __DIR__ . '/..' . '/symfony/validator/DependencyInjection/AddAutoMappingConfigurationPass.php',
+ 'Symfony\\Component\\Validator\\DependencyInjection\\AddConstraintValidatorsPass' => __DIR__ . '/..' . '/symfony/validator/DependencyInjection/AddConstraintValidatorsPass.php',
+ 'Symfony\\Component\\Validator\\DependencyInjection\\AddValidatorInitializersPass' => __DIR__ . '/..' . '/symfony/validator/DependencyInjection/AddValidatorInitializersPass.php',
+ 'Symfony\\Component\\Validator\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/symfony/validator/Exception/BadMethodCallException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ConstraintDefinitionException' => __DIR__ . '/..' . '/symfony/validator/Exception/ConstraintDefinitionException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/validator/Exception/ExceptionInterface.php',
+ 'Symfony\\Component\\Validator\\Exception\\GroupDefinitionException' => __DIR__ . '/..' . '/symfony/validator/Exception/GroupDefinitionException.php',
+ 'Symfony\\Component\\Validator\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/validator/Exception/InvalidArgumentException.php',
+ 'Symfony\\Component\\Validator\\Exception\\InvalidOptionsException' => __DIR__ . '/..' . '/symfony/validator/Exception/InvalidOptionsException.php',
+ 'Symfony\\Component\\Validator\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/validator/Exception/LogicException.php',
+ 'Symfony\\Component\\Validator\\Exception\\MappingException' => __DIR__ . '/..' . '/symfony/validator/Exception/MappingException.php',
+ 'Symfony\\Component\\Validator\\Exception\\MissingOptionsException' => __DIR__ . '/..' . '/symfony/validator/Exception/MissingOptionsException.php',
+ 'Symfony\\Component\\Validator\\Exception\\NoSuchMetadataException' => __DIR__ . '/..' . '/symfony/validator/Exception/NoSuchMetadataException.php',
+ 'Symfony\\Component\\Validator\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/validator/Exception/OutOfBoundsException.php',
+ 'Symfony\\Component\\Validator\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/validator/Exception/RuntimeException.php',
+ 'Symfony\\Component\\Validator\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/validator/Exception/UnexpectedTypeException.php',
+ 'Symfony\\Component\\Validator\\Exception\\UnexpectedValueException' => __DIR__ . '/..' . '/symfony/validator/Exception/UnexpectedValueException.php',
+ 'Symfony\\Component\\Validator\\Exception\\UnsupportedMetadataException' => __DIR__ . '/..' . '/symfony/validator/Exception/UnsupportedMetadataException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ValidationFailedException' => __DIR__ . '/..' . '/symfony/validator/Exception/ValidationFailedException.php',
+ 'Symfony\\Component\\Validator\\Exception\\ValidatorException' => __DIR__ . '/..' . '/symfony/validator/Exception/ValidatorException.php',
+ 'Symfony\\Component\\Validator\\GroupProviderInterface' => __DIR__ . '/..' . '/symfony/validator/GroupProviderInterface.php',
+ 'Symfony\\Component\\Validator\\GroupSequenceProviderInterface' => __DIR__ . '/..' . '/symfony/validator/GroupSequenceProviderInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\AutoMappingStrategy' => __DIR__ . '/..' . '/symfony/validator/Mapping/AutoMappingStrategy.php',
+ 'Symfony\\Component\\Validator\\Mapping\\CascadingStrategy' => __DIR__ . '/..' . '/symfony/validator/Mapping/CascadingStrategy.php',
+ 'Symfony\\Component\\Validator\\Mapping\\ClassMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/ClassMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\ClassMetadataInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/ClassMetadataInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Factory\\BlackHoleMetadataFactory' => __DIR__ . '/..' . '/symfony/validator/Mapping/Factory/BlackHoleMetadataFactory.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Factory\\LazyLoadingMetadataFactory' => __DIR__ . '/..' . '/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Factory\\MetadataFactoryInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\GenericMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/GenericMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\GetterMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/GetterMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AbstractLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/AbstractLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AnnotationLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/AnnotationLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AttributeLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/AttributeLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\AutoMappingTrait' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/AutoMappingTrait.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/FileLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\FilesLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/FilesLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderChain' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/LoaderChain.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/LoaderInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\PropertyInfoLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/PropertyInfoLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\StaticMethodLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/StaticMethodLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/XmlFileLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFilesLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/XmlFilesLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/YamlFileLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFilesLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/YamlFilesLoader.php',
+ 'Symfony\\Component\\Validator\\Mapping\\MemberMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/MemberMetadata.php',
+ 'Symfony\\Component\\Validator\\Mapping\\MetadataInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/MetadataInterface.php',
+ 'Symfony\\Component\\Validator\\Mapping\\PropertyMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/PropertyMetadata.php',
+ '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\\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',
+ 'Symfony\\Component\\Validator\\Validator\\ContextualValidatorInterface' => __DIR__ . '/..' . '/symfony/validator/Validator/ContextualValidatorInterface.php',
+ 'Symfony\\Component\\Validator\\Validator\\LazyProperty' => __DIR__ . '/..' . '/symfony/validator/Validator/LazyProperty.php',
+ 'Symfony\\Component\\Validator\\Validator\\RecursiveContextualValidator' => __DIR__ . '/..' . '/symfony/validator/Validator/RecursiveContextualValidator.php',
+ 'Symfony\\Component\\Validator\\Validator\\RecursiveValidator' => __DIR__ . '/..' . '/symfony/validator/Validator/RecursiveValidator.php',
+ 'Symfony\\Component\\Validator\\Validator\\TraceableValidator' => __DIR__ . '/..' . '/symfony/validator/Validator/TraceableValidator.php',
+ 'Symfony\\Component\\Validator\\Validator\\ValidatorInterface' => __DIR__ . '/..' . '/symfony/validator/Validator/ValidatorInterface.php',
+ 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilder' => __DIR__ . '/..' . '/symfony/validator/Violation/ConstraintViolationBuilder.php',
+ 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilderInterface' => __DIR__ . '/..' . '/symfony/validator/Violation/ConstraintViolationBuilderInterface.php',
'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/AmqpCaster.php',
'Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ArgsStub.php',
'Symfony\\Component\\VarDumper\\Caster\\Caster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/Caster.php',
diff --git a/lib/composer/installed.json b/lib/composer/installed.json
index 5ef18f98a8..f8383b72fe 100644
--- a/lib/composer/installed.json
+++ b/lib/composer/installed.json
@@ -5238,6 +5238,110 @@
],
"install-path": "../symfony/twig-bundle"
},
+ {
+ "name": "symfony/validator",
+ "version": "v6.4.29",
+ "version_normalized": "6.4.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/validator.git",
+ "reference": "99df8a769e64e399f510166141ea74f450e8dd1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/99df8a769e64e399f510166141ea74f450e8dd1d",
+ "reference": "99df8a769e64e399f510166141ea74f450e8dd1d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php83": "^1.27",
+ "symfony/translation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "doctrine/annotations": "<1.13",
+ "doctrine/lexer": "<1.1",
+ "symfony/dependency-injection": "<5.4",
+ "symfony/expression-language": "<5.4",
+ "symfony/http-kernel": "<5.4",
+ "symfony/intl": "<5.4",
+ "symfony/property-info": "<5.4",
+ "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3",
+ "symfony/yaml": "<5.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^1.13|^2",
+ "egulias/email-validator": "^2.1.10|^3|^4",
+ "symfony/cache": "^5.4|^6.0|^7.0",
+ "symfony/config": "^5.4|^6.0|^7.0",
+ "symfony/console": "^5.4|^6.0|^7.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/expression-language": "^5.4|^6.0|^7.0",
+ "symfony/finder": "^5.4|^6.0|^7.0",
+ "symfony/http-client": "^5.4|^6.0|^7.0",
+ "symfony/http-foundation": "^5.4|^6.0|^7.0",
+ "symfony/http-kernel": "^5.4|^6.0|^7.0",
+ "symfony/intl": "^5.4|^6.0|^7.0",
+ "symfony/mime": "^5.4|^6.0|^7.0",
+ "symfony/property-access": "^5.4|^6.0|^7.0",
+ "symfony/property-info": "^5.4|^6.0|^7.0",
+ "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3",
+ "symfony/yaml": "^5.4|^6.0|^7.0"
+ },
+ "time": "2025-11-06T20:26:06+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Validator\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/",
+ "/Resources/bin/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools to validate values",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/validator/tree/v6.4.29"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/validator"
+ },
{
"name": "symfony/var-dumper",
"version": "v6.4.26",
diff --git a/lib/composer/installed.php b/lib/composer/installed.php
index 33b2f7d8a7..ff4da6e4b6 100644
--- a/lib/composer/installed.php
+++ b/lib/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'combodo/itop',
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
- 'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
+ 'reference' => '06e5c8078690380fb8690371a69eb3c68469c1ed',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -22,7 +22,7 @@
'combodo/itop' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
- 'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
+ 'reference' => '06e5c8078690380fb8690371a69eb3c68469c1ed',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -678,6 +678,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'symfony/validator' => array(
+ 'pretty_version' => 'v6.4.29',
+ 'version' => '6.4.29.0',
+ 'reference' => '99df8a769e64e399f510166141ea74f450e8dd1d',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/validator',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'symfony/var-dumper' => array(
'pretty_version' => 'v6.4.26',
'version' => '6.4.26.0',
diff --git a/lib/soundasleep/html2text/phpstan.neon.dist b/lib/soundasleep/html2text/phpstan.neon.dist
new file mode 100644
index 0000000000..b05dc95119
--- /dev/null
+++ b/lib/soundasleep/html2text/phpstan.neon.dist
@@ -0,0 +1,7 @@
+parameters:
+ level: 6
+ errorFormat: raw
+ editorUrl: '%%file%% %%line%% %%column%%: %%error%%'
+ paths:
+ - src
+ - tests
diff --git a/lib/symfony/form/FormBuilder.php b/lib/symfony/form/FormBuilder.php
index 3c0f4e2179..beabdd03c8 100644
--- a/lib/symfony/form/FormBuilder.php
+++ b/lib/symfony/form/FormBuilder.php
@@ -26,192 +26,192 @@
*/
class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormBuilderInterface
{
- /**
- * The children of the form builder.
- *
- * @var FormBuilderInterface[]
- */
- private array $children = [];
+ /**
+ * The children of the form builder.
+ *
+ * @var FormBuilderInterface[]
+ */
+ private array $children = [];
- /**
- * The data of children who haven't been converted to form builders yet.
- */
- private array $unresolvedChildren = [];
-
- public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, FormFactoryInterface $factory, array $options = [])
- {
- parent::__construct($name, $dataClass, $dispatcher, $options);
-
- $this->setFormFactory($factory);
- }
+ /**
+ * The data of children who haven't been converted to form builders yet.
+ */
+ private array $unresolvedChildren = [];
+
+ public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, FormFactoryInterface $factory, array $options = [])
+ {
+ parent::__construct($name, $dataClass, $dispatcher, $options);
+
+ $this->setFormFactory($factory);
+ }
- public function add(FormBuilderInterface|string $child, ?string $type = null, array $options = []): static
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- if ($child instanceof FormBuilderInterface) {
- $this->children[$child->getName()] = $child;
-
- // In case an unresolved child with the same name exists
- unset($this->unresolvedChildren[$child->getName()]);
+ public function add(FormBuilderInterface|string $child, ?string $type = null, array $options = []): static
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ if ($child instanceof FormBuilderInterface) {
+ $this->children[$child->getName()] = $child;
+
+ // In case an unresolved child with the same name exists
+ unset($this->unresolvedChildren[$child->getName()]);
- return $this;
- }
-
- if (!\is_string($child) && !\is_int($child)) {
- throw new UnexpectedTypeException($child, 'string or Symfony\Component\Form\FormBuilderInterface');
- }
+ return $this;
+ }
+
+ if (!\is_string($child) && !\is_int($child)) {
+ throw new UnexpectedTypeException($child, 'string or Symfony\Component\Form\FormBuilderInterface');
+ }
- // Add to "children" to maintain order
- $this->children[$child] = null;
- $this->unresolvedChildren[$child] = [$type, $options];
+ // Add to "children" to maintain order
+ $this->children[$child] = null;
+ $this->unresolvedChildren[$child] = [$type, $options];
- return $this;
- }
+ return $this;
+ }
- public function create(string $name, ?string $type = null, array $options = []): FormBuilderInterface
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
+ public function create(string $name, ?string $type = null, array $options = []): FormBuilderInterface
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
- if (null === $type && null === $this->getDataClass()) {
- $type = TextType::class;
- }
+ if (null === $type && null === $this->getDataClass()) {
+ $type = TextType::class;
+ }
- if (null !== $type) {
- return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options);
- }
-
- return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options);
- }
-
- public function get(string $name): FormBuilderInterface
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- if (isset($this->unresolvedChildren[$name])) {
- return $this->resolveChild($name);
- }
-
- if (isset($this->children[$name])) {
- return $this->children[$name];
- }
-
- throw new InvalidArgumentException(\sprintf('The child with the name "%s" does not exist.', $name));
- }
-
- public function remove(string $name): static
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- unset($this->unresolvedChildren[$name], $this->children[$name]);
-
- return $this;
- }
-
- public function has(string $name): bool
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]);
- }
-
- public function all(): array
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- $this->resolveChildren();
-
- return $this->children;
- }
-
- public function count(): int
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- return \count($this->children);
- }
-
- public function getFormConfig(): FormConfigInterface
- {
- /** @var self $config */
- $config = parent::getFormConfig();
-
- $config->children = [];
- $config->unresolvedChildren = [];
-
- return $config;
- }
-
- public function getForm(): FormInterface
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- $this->resolveChildren();
-
- $form = new Form($this->getFormConfig());
-
- foreach ($this->children as $child) {
- // Automatic initialization is only supported on root forms
- $form->add($child->setAutoInitialize(false)->getForm());
- }
-
- if ($this->getAutoInitialize()) {
- // Automatically initialize the form if it is configured so
- $form->initialize();
- }
-
- return $form;
- }
-
- /**
- * @return \Traversable
- */
- public function getIterator(): \Traversable
- {
- if ($this->locked) {
- throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
- }
-
- return new \ArrayIterator($this->all());
- }
-
- /**
- * Converts an unresolved child into a {@link FormBuilderInterface} instance.
- */
- private function resolveChild(string $name): FormBuilderInterface
- {
- [$type, $options] = $this->unresolvedChildren[$name];
-
- unset($this->unresolvedChildren[$name]);
-
- return $this->children[$name] = $this->create($name, $type, $options);
- }
-
- /**
- * Converts all unresolved children into {@link FormBuilder} instances.
- */
- private function resolveChildren(): void
- {
- foreach ($this->unresolvedChildren as $name => $info) {
- $this->children[$name] = $this->create($name, $info[0], $info[1]);
- }
-
- $this->unresolvedChildren = [];
- }
+ if (null !== $type) {
+ return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options);
+ }
+
+ return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options);
+ }
+
+ public function get(string $name): FormBuilderInterface
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ if (isset($this->unresolvedChildren[$name])) {
+ return $this->resolveChild($name);
+ }
+
+ if (isset($this->children[$name])) {
+ return $this->children[$name];
+ }
+
+ throw new InvalidArgumentException(\sprintf('The child with the name "%s" does not exist.', $name));
+ }
+
+ public function remove(string $name): static
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ unset($this->unresolvedChildren[$name], $this->children[$name]);
+
+ return $this;
+ }
+
+ public function has(string $name): bool
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]);
+ }
+
+ public function all(): array
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ $this->resolveChildren();
+
+ return $this->children;
+ }
+
+ public function count(): int
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ return \count($this->children);
+ }
+
+ public function getFormConfig(): FormConfigInterface
+ {
+ /** @var self $config */
+ $config = parent::getFormConfig();
+
+ $config->children = [];
+ $config->unresolvedChildren = [];
+
+ return $config;
+ }
+
+ public function getForm(): FormInterface
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ $this->resolveChildren();
+
+ $form = new Form($this->getFormConfig());
+
+ foreach ($this->children as $child) {
+ // Automatic initialization is only supported on root forms
+ $form->add($child->setAutoInitialize(false)->getForm());
+ }
+
+ if ($this->getAutoInitialize()) {
+ // Automatically initialize the form if it is configured so
+ $form->initialize();
+ }
+
+ return $form;
+ }
+
+ /**
+ * @return \Traversable
+ */
+ public function getIterator(): \Traversable
+ {
+ if ($this->locked) {
+ throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
+ }
+
+ return new \ArrayIterator($this->all());
+ }
+
+ /**
+ * Converts an unresolved child into a {@link FormBuilderInterface} instance.
+ */
+ private function resolveChild(string $name): FormBuilderInterface
+ {
+ [$type, $options] = $this->unresolvedChildren[$name];
+
+ unset($this->unresolvedChildren[$name]);
+
+ return $this->children[$name] = $this->create($name, $type, $options);
+ }
+
+ /**
+ * Converts all unresolved children into {@link FormBuilder} instances.
+ */
+ private function resolveChildren(): void
+ {
+ foreach ($this->unresolvedChildren as $name => $info) {
+ $this->children[$name] = $this->create($name, $info[0], $info[1]);
+ }
+
+ $this->unresolvedChildren = [];
+ }
}
diff --git a/lib/symfony/mime/LICENSE b/lib/symfony/mime/LICENSE
new file mode 100644
index 0000000000..4dd83ce0f1
--- /dev/null
+++ b/lib/symfony/mime/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/lib/symfony/validator/Attribute/HasNamedArguments.php b/lib/symfony/validator/Attribute/HasNamedArguments.php
new file mode 100644
index 0000000000..cc16629436
--- /dev/null
+++ b/lib/symfony/validator/Attribute/HasNamedArguments.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Attribute;
+
+#[\Attribute(\Attribute::TARGET_METHOD)]
+final class HasNamedArguments
+{
+}
diff --git a/lib/symfony/validator/CHANGELOG.md b/lib/symfony/validator/CHANGELOG.md
new file mode 100644
index 0000000000..5660c2f4e4
--- /dev/null
+++ b/lib/symfony/validator/CHANGELOG.md
@@ -0,0 +1,415 @@
+CHANGELOG
+=========
+
+6.4
+---
+
+ * Add `is_valid` function to the `Expression` constraint, its behavior is the same as `ValidatorInterface::validate`
+ * Allow single integer for the `versions` option of the `Uuid` constraint
+ * Allow single constraint to be passed to the `constraints` option of the `When` constraint
+ * Deprecate Doctrine annotations support in favor of native attributes
+ * Deprecate `ValidatorBuilder::setDoctrineAnnotationReader()`
+ * Deprecate `ValidatorBuilder::addDefaultDoctrineAnnotationReader()`
+ * Add `number`, `finite-number` and `finite-float` types to `Type` constraint
+ * Add the `withSeconds` option to the `Time` constraint that allows to pass time without seconds
+ * Deprecate `ValidatorBuilder::enableAnnotationMapping()`, use `ValidatorBuilder::enableAttributeMapping()` instead
+ * Deprecate `ValidatorBuilder::disableAnnotationMapping()`, use `ValidatorBuilder::disableAttributeMapping()` instead
+ * Deprecate `AnnotationLoader`, use `AttributeLoader` instead
+ * Add `GroupProviderInterface` to implement validation group providers outside the underlying class
+
+6.3
+---
+
+ * Add method `getConstraint()` to `ConstraintViolationInterface`
+ * Add `Uuid::TIME_BASED_VERSIONS` to match that a UUID being validated embeds a timestamp
+ * Add the `pattern` parameter in violations of the `Regex` constraint
+ * Add a `NoSuspiciousCharacters` constraint to validate a string is not a spoofing attempt
+ * Add a `PasswordStrength` constraint to check the strength of a password
+ * Add the `countUnit` option to the `Length` constraint to allow counting the string length either by code points (like before, now the default setting `Length::COUNT_CODEPOINTS`), bytes (`Length::COUNT_BYTES`) or graphemes (`Length::COUNT_GRAPHEMES`)
+ * Add the `filenameMaxLength` option to the `File` constraint
+ * Add the `exclude` option to the `Cascade` constraint
+ * Add the `value_length` parameter to `Length` constraint
+ * Allow to disable the translation domain for `ConstraintViolationInterface` messages
+
+6.2
+---
+
+ * Add option `Email::VALIDATION_MODE_HTML5_ALLOW_NO_TLD` with "html5-allow-no-tld" e-mail validation mode, to match with the W3C official specification
+ * Add method `getCause()` to `ConstraintViolationInterface`
+ * Add the `When` constraint and validator
+ * Deprecate the "loose" e-mail validation mode, use "html5" instead
+ * Add the `negate` option to the `Expression` constraint, to inverse the logic of the violation's creation
+ * Add the `extensions` option to the `File` constraint as an alternative to `mimeTypes` which checks the mime type of the file, its extension, and the consistency between them
+ * Add padding for enhanced privacy to the `NotCompromisedPasswordValidator`
+
+6.1
+---
+
+ * Add the `fields` option to the `Unique` constraint, to define which collection keys should be checked for uniqueness
+ * Deprecate `Constraint::$errorNames`, use `Constraint::ERROR_NAMES` instead
+ * Deprecate constraint `ExpressionLanguageSyntax`, use `ExpressionSyntax` instead
+ * Add method `__toString()` to `ConstraintViolationInterface` & `ConstraintViolationListInterface`
+ * Allow creating constraints with required arguments
+ * Add the `match` option to the `Choice` constraint
+
+6.0
+---
+
+ * Remove the `allowEmptyString` option from the `Length` constraint
+ * Remove the `NumberConstraintTrait` trait
+ * `ValidatorBuilder::enableAnnotationMapping()` does not accept a Doctrine annotation reader anymore
+ * `ValidatorBuilder::enableAnnotationMapping()` won't automatically setup a Doctrine annotation reader anymore
+
+5.4
+---
+
+ * Add a `Cidr` constraint to validate CIDR notations
+ * Add a `CssColor` constraint to validate CSS colors
+ * Add support for `ConstraintViolationList::createFromMessage()`
+ * Add error's uid to `Count` and `Length` constraints with "exactly" option enabled
+
+5.3
+---
+
+ * Add the `normalizer` option to the `Unique` constraint
+ * Add `Validation::createIsValidCallable()` that returns true/false instead of throwing exceptions
+
+5.2.0
+-----
+
+ * added a `Cascade` constraint to ease validating nested typed object properties
+ * deprecated the `allowEmptyString` option of the `Length` constraint
+
+ Before:
+
+ ```php
+ use Symfony\Component\Validator\Constraints as Assert;
+
+ /**
+ * @Assert\Length(min=5, allowEmptyString=true)
+ */
+ ```
+
+ After:
+
+ ```php
+ use Symfony\Component\Validator\Constraints as Assert;
+
+ /**
+ * @Assert\AtLeastOneOf({
+ * @Assert\Blank(),
+ * @Assert\Length(min=5)
+ * })
+ */
+ ```
+ * added the `Isin` constraint and validator
+ * added the `ULID` constraint and validator
+ * added support for UUIDv6 in `Uuid` constraint
+ * enabled the validator to load constraints from PHP attributes
+ * deprecated the `NumberConstraintTrait` trait
+ * deprecated setting or creating a Doctrine annotation reader via `ValidatorBuilder::enableAnnotationMapping()`, pass `true` as first parameter and additionally call `setDoctrineAnnotationReader()` or `addDefaultDoctrineAnnotationReader()` to set up the annotation reader
+
+5.1.0
+-----
+
+ * Add `AtLeastOneOf` constraint that is considered to be valid if at least one of the nested constraints is valid
+ * added the `Hostname` constraint and validator
+ * added the `alpha3` option to the `Country` and `Language` constraints
+ * allow to define a reusable set of constraints by extending the `Compound` constraint
+ * added `Sequentially` constraint, to sequentially validate a set of constraints (any violation raised will prevent further validation of the nested constraints)
+ * added the `divisibleBy` option to the `Count` constraint
+ * added the `ExpressionLanguageSyntax` constraint
+
+5.0.0
+-----
+
+ * an `ExpressionLanguage` instance or null must be passed as the first argument of `ExpressionValidator::__construct()`
+ * removed the `checkDNS` and `dnsMessage` options of the `Url` constraint
+ * removed the `checkMX`, `checkHost` and `strict` options of the `Email` constraint
+ * removed support for validating instances of `\DateTimeInterface` in `DateTimeValidator`, `DateValidator` and `TimeValidator`
+ * removed support for using the `Bic`, `Country`, `Currency`, `Language` and `Locale` constraints without `symfony/intl`
+ * removed support for using the `Email` constraint without `egulias/email-validator`
+ * removed support for using the `Expression` constraint without `symfony/expression-language`
+ * changed default value of `canonicalize` option of `Locale` constraint to `true`
+ * removed `ValidatorBuilderInterface`
+ * passing a null message when instantiating a `ConstraintViolation` is not allowed
+ * changed the default value of `Length::$allowEmptyString` to `false` and made it optional
+ * removed `Symfony\Component\Validator\Mapping\Cache\CacheInterface` in favor of PSR-6.
+ * removed `ValidatorBuilder::setMetadataCache`, use `ValidatorBuilder::setMappingCache` instead.
+
+4.4.0
+-----
+
+ * [BC BREAK] using null as `$classValidatorRegexp` value in `PropertyInfoLoader::__construct` will not enable auto-mapping for all classes anymore, use `'{.*}'` instead.
+ * added `EnableAutoMapping` and `DisableAutoMapping` constraints to enable or disable auto mapping for class or a property
+ * using anything else than a `string` as the code of a `ConstraintViolation` is deprecated, a `string` type-hint will
+ be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()`
+ method in 5.0
+ * deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. Pass it as the first argument instead.
+ * added the `compared_value_path` parameter in violations when using any
+ comparison constraint with the `propertyPath` option.
+ * added support for checking an array of types in `TypeValidator`
+ * added a new `allowEmptyString` option to the `Length` constraint to allow rejecting empty strings when `min` is set, by setting it to `false`.
+ * Added new `minPropertyPath` and `maxPropertyPath` options
+ to `Range` constraint in order to get the value to compare
+ from an array or object
+ * added the `min_limit_path` and `max_limit_path` parameters in violations when using
+ `Range` constraint with respectively the `minPropertyPath` and
+ `maxPropertyPath` options
+ * added a new `notInRangeMessage` option to the `Range` constraint that will
+ be used in the violation builder when both `min` and `max` are not null
+ * added ability to use stringable objects as violation messages
+ * Overriding the methods `ConstraintValidatorTestCase::setUp()` and `ConstraintValidatorTestCase::tearDown()` without the `void` return-type is deprecated.
+ * deprecated `Symfony\Component\Validator\Mapping\Cache\CacheInterface` in favor of PSR-6.
+ * deprecated `ValidatorBuilder::setMetadataCache`, use `ValidatorBuilder::setMappingCache` instead.
+ * Marked the `ValidatorDataCollector` class as `@final`.
+
+4.3.0
+-----
+
+ * added `Timezone` constraint
+ * added `NotCompromisedPassword` constraint
+ * added options `iban` and `ibanPropertyPath` to Bic constraint
+ * added UATP cards support to `CardSchemeValidator`
+ * added option `allowNull` to NotBlank constraint
+ * added `Json` constraint
+ * added `Unique` constraint
+ * added a new `normalizer` option to the string constraints and to the `NotBlank` constraint
+ * added `Positive` constraint
+ * added `PositiveOrZero` constraint
+ * added `Negative` constraint
+ * added `NegativeOrZero` constraint
+
+4.2.0
+-----
+
+ * added a new `UnexpectedValueException` that can be thrown by constraint validators, these exceptions are caught by
+ the validator and are converted into constraint violations
+ * added `DivisibleBy` constraint
+ * decoupled from `symfony/translation` by using `Symfony\Contracts\Translation\TranslatorInterface`
+ * deprecated `ValidatorBuilderInterface`
+ * made `ValidatorBuilder::setTranslator()` final
+ * marked `format` the default option in `DateTime` constraint
+ * deprecated validating instances of `\DateTimeInterface` in `DateTimeValidator`, `DateValidator` and `TimeValidator`.
+ * deprecated using the `Bic`, `Country`, `Currency`, `Language` and `Locale` constraints without `symfony/intl`
+ * deprecated using the `Email` constraint without `egulias/email-validator`
+ * deprecated using the `Expression` constraint without `symfony/expression-language`
+
+4.1.0
+-----
+
+ * Deprecated the `checkDNS` and `dnsMessage` options of the `Url` constraint.
+ * added a `values` option to the `Expression` constraint
+ * Deprecated use of `Locale` constraint without setting `true` at "canonicalize" option, which will be the default value in 5.0
+
+4.0.0
+-----
+
+ * Setting the `strict` option of the `Choice` constraint to anything but `true`
+ is not supported anymore.
+ * removed the `DateTimeValidator::PATTERN` constant
+ * removed the `AbstractConstraintValidatorTest` class
+ * removed support for setting the `checkDNS` option of the `Url` constraint to `true`
+
+3.4.0
+-----
+
+ * added support for validation groups to the `Valid` constraint
+ * not setting the `strict` option of the `Choice` constraint to `true` is
+ deprecated and will throw an exception in Symfony 4.0
+ * setting the `checkDNS` option of the `Url` constraint to `true` is deprecated in favor of
+ the `Url::CHECK_DNS_TYPE_*` constants values and will throw an exception in Symfony 4.0
+ * added min/max amount of pixels check to `Image` constraint via `minPixels` and `maxPixels`
+ * added a new "propertyPath" option to comparison constraints in order to get the value to compare from an array or object
+
+3.3.0
+-----
+
+ * added `AddValidatorInitializersPass`
+ * added `AddConstraintValidatorsPass`
+ * added `ContainerConstraintValidatorFactory`
+
+3.2.0
+-----
+
+ * deprecated `Tests\Constraints\AbstractConstraintValidatorTest` in favor of `Test\ConstraintValidatorTestCase`
+ * added support for PHP constants in YAML configuration files
+
+3.1.0
+-----
+
+ * deprecated `DateTimeValidator::PATTERN` constant
+ * added a `format` option to the `DateTime` constraint
+
+2.8.0
+-----
+
+ * added the BIC (SWIFT-Code) validator
+
+2.7.0
+-----
+
+ * deprecated `DefaultTranslator` in favor of `Symfony\Component\Translation\IdentityTranslator`
+ * deprecated PHP7-incompatible constraints (Null, True, False) and related validators (NullValidator, TrueValidator, FalseValidator) in favor of their `Is`-prefixed equivalent
+
+2.6.0
+-----
+
+ * [BC BREAK] `FileValidator` disallow empty files
+ * [BC BREAK] `UserPasswordValidator` source message change
+ * [BC BREAK] added internal `ExecutionContextInterface::setConstraint()`
+ * added `ConstraintViolation::getConstraint()`
+ * [BC BREAK] The `ExpressionValidator` will now evaluate the Expression even when the property value is null or an empty string
+ * deprecated `ClassMetadata::hasMemberMetadatas()`
+ * deprecated `ClassMetadata::getMemberMetadatas()`
+ * deprecated `ClassMetadata::addMemberMetadata()`
+ * [BC BREAK] added `Mapping\MetadataInterface::getConstraints()`
+ * added generic "payload" option to all constraints for attaching domain-specific data
+ * [BC BREAK] added `ConstraintViolationBuilderInterface::setCause()`
+
+2.5.0
+-----
+
+ * deprecated `ApcCache` in favor of `DoctrineCache`
+ * added `DoctrineCache` to adapt any Doctrine cache
+ * `GroupSequence` now implements `ArrayAccess`, `Countable` and `Traversable`
+ * [BC BREAK] changed `ClassMetadata::getGroupSequence()` to return a `GroupSequence` instance instead of an array
+ * `Callback` can now be put onto properties (useful when you pass a closure to the constraint)
+ * deprecated `ClassBasedInterface`
+ * deprecated `MetadataInterface`
+ * deprecated `PropertyMetadataInterface`
+ * deprecated `PropertyMetadataContainerInterface`
+ * deprecated `Mapping\ElementMetadata`
+ * added `Mapping\MetadataInterface`
+ * added `Mapping\ClassMetadataInterface`
+ * added `Mapping\PropertyMetadataInterface`
+ * added `Mapping\GenericMetadata`
+ * added `Mapping\CascadingStrategy`
+ * added `Mapping\TraversalStrategy`
+ * deprecated `Mapping\ClassMetadata::accept()`
+ * deprecated `Mapping\MemberMetadata::accept()`
+ * removed array type hint of `Mapping\ClassMetadata::setGroupSequence()`
+ * deprecated `MetadataFactoryInterface`
+ * deprecated `Mapping\BlackholeMetadataFactory`
+ * deprecated `Mapping\ClassMetadataFactory`
+ * added `Mapping\Factory\MetadataFactoryInterface`
+ * added `Mapping\Factory\BlackHoleMetadataFactory`
+ * added `Mapping\Factory\LazyLoadingMetadataFactory`
+ * deprecated `ExecutionContextInterface`
+ * deprecated `ExecutionContext`
+ * deprecated `GlobalExecutionContextInterface`
+ * added `Context\ExecutionContextInterface`
+ * added `Context\ExecutionContext`
+ * added `Context\ExecutionContextFactoryInterface`
+ * added `Context\ExecutionContextFactory`
+ * deprecated `ValidatorInterface`
+ * deprecated `Validator`
+ * deprecated `ValidationVisitorInterface`
+ * deprecated `ValidationVisitor`
+ * added `Validator\ValidatorInterface`
+ * added `Validator\RecursiveValidator`
+ * added `Validator\ContextualValidatorInterface`
+ * added `Validator\RecursiveContextualValidator`
+ * added `Violation\ConstraintViolationBuilderInterface`
+ * added `Violation\ConstraintViolationBuilder`
+ * added `ConstraintViolation::getParameters()`
+ * added `ConstraintViolation::getPlural()`
+ * added `Constraints\Traverse`
+ * deprecated `$deep` property in `Constraints\Valid`
+ * added `ValidatorBuilderInterface::setApiVersion()`
+ * added `Validation::API_VERSION_2_4`
+ * added `Validation::API_VERSION_2_5`
+ * added `Exception\OutOfBoundsException`
+ * added `Exception\UnsupportedMetadataException`
+ * made `Exception\ValidatorException` extend `Exception\RuntimeException`
+ * added `Util\PropertyPath`
+ * made the PropertyAccess component an optional dependency
+ * deprecated `ValidatorBuilder::setPropertyAccessor()`
+ * deprecated `validate` and `validateValue` on `Validator\Context\ExecutionContext` use `getValidator()` together with `inContext()` instead
+
+2.4.0
+-----
+
+ * added a constraint the uses the expression language
+ * added `minRatio`, `maxRatio`, `allowSquare`, `allowLandscape`, and `allowPortrait` to Image validator
+
+2.3.29
+------
+
+ * fixed compatibility with PHP7 and up by introducing new constraints (IsNull, IsTrue, IsFalse) and related validators (IsNullValidator, IsTrueValidator, IsFalseValidator)
+
+2.3.0
+-----
+
+ * added the ISBN, ISSN, and IBAN validators
+ * copied the constraints `Optional` and `Required` to the
+ `Symfony\Component\Validator\Constraints\` namespace and deprecated the original
+ classes.
+ * added comparison validators (EqualTo, NotEqualTo, LessThan, LessThanOrEqualTo, GreaterThan, GreaterThanOrEqualTo, IdenticalTo, NotIdenticalTo)
+
+2.2.0
+-----
+
+ * added a CardScheme validator
+ * added a Luhn validator
+ * moved @api-tags from `Validator` to `ValidatorInterface`
+ * moved @api-tags from `ConstraintViolation` to the new `ConstraintViolationInterface`
+ * moved @api-tags from `ConstraintViolationList` to the new `ConstraintViolationListInterface`
+ * moved @api-tags from `ExecutionContext` to the new `ExecutionContextInterface`
+ * [BC BREAK] `ConstraintValidatorInterface::initialize` is now type hinted against `ExecutionContextInterface` instead of `ExecutionContext`
+ * [BC BREAK] changed the visibility of the properties in `Validator` from protected to private
+ * deprecated `ClassMetadataFactoryInterface` in favor of the new `MetadataFactoryInterface`
+ * deprecated `ClassMetadataFactory::getClassMetadata` in favor of `getMetadataFor`
+ * created `MetadataInterface`, `PropertyMetadataInterface`, `ClassBasedInterface` and `PropertyMetadataContainerInterface`
+ * deprecated `GraphWalker` in favor of the new `ValidationVisitorInterface`
+ * deprecated `ExecutionContext::addViolationAtPath`
+ * deprecated `ExecutionContext::addViolationAtSubPath` in favor of `ExecutionContextInterface::addViolationAt`
+ * deprecated `ExecutionContext::getCurrentClass` in favor of `ExecutionContextInterface::getClassName`
+ * deprecated `ExecutionContext::getCurrentProperty` in favor of `ExecutionContextInterface::getPropertyName`
+ * deprecated `ExecutionContext::getCurrentValue` in favor of `ExecutionContextInterface::getValue`
+ * deprecated `ExecutionContext::getGraphWalker` in favor of `ExecutionContextInterface::validate` and `ExecutionContextInterface::validateValue`
+ * improved `ValidatorInterface::validateValue` to accept arrays of constraints
+ * changed `ValidatorInterface::getMetadataFactory` to return a `MetadataFactoryInterface` instead of a `ClassMetadataFactoryInterface`
+ * removed `ClassMetadataFactoryInterface` type hint from `ValidatorBuilderInterface::setMetadataFactory`.
+ As of Symfony 2.3, this method will be typed against `MetadataFactoryInterface` instead.
+ * [BC BREAK] the switches `traverse` and `deep` in the `Valid` constraint and in `GraphWalker::walkReference`
+ are ignored for arrays now. Arrays are always traversed recursively.
+ * added dependency to Translation component
+ * violation messages are now translated with a TranslatorInterface implementation
+ * [BC BREAK] inserted argument `$message` in the constructor of `ConstraintViolation`
+ * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `ExecutionContext`
+ * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `GraphWalker`
+ * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `ValidationVisitor`
+ * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `Validator`
+ * [BC BREAK] added `setTranslator()` and `setTranslationDomain()` to `ValidatorBuilderInterface`
+ * improved the Validator to support pluralized messages by default
+ * [BC BREAK] changed the source of all pluralized messages in the translation files to the pluralized version
+ * added ExceptionInterface, BadMethodCallException and InvalidArgumentException
+
+2.1.0
+-----
+
+ * added support for `ctype_*` assertions in `TypeValidator`
+ * improved the ImageValidator with min width, max width, min height, and max height constraints
+ * added support for MIME with wildcard in FileValidator
+ * changed Collection validator to add "missing" and "extra" errors to
+ individual fields
+ * changed default value for `extraFieldsMessage` and `missingFieldsMessage`
+ in Collection constraint
+ * made ExecutionContext immutable
+ * deprecated Constraint methods `setMessage`, `getMessageTemplate` and
+ `getMessageParameters`
+ * added support for dynamic group sequences with the GroupSequenceProvider pattern
+ * [BC BREAK] ConstraintValidatorInterface method `isValid` has been renamed to
+ `validate`, its return value was dropped. ConstraintValidator still contains
+ `isValid` for BC
+ * [BC BREAK] collections in fields annotated with `Valid` are not traversed
+ recursively anymore by default. `Valid` contains a new property `deep`
+ which enables the BC behavior.
+ * added Count constraint
+ * added Length constraint
+ * added Range constraint
+ * deprecated the Min and Max constraints
+ * deprecated the MinLength and MaxLength constraints
+ * added Validation and ValidatorBuilderInterface
+ * deprecated ValidatorContext, ValidatorContextInterface and ValidatorFactory
diff --git a/lib/symfony/validator/Command/DebugCommand.php b/lib/symfony/validator/Command/DebugCommand.php
new file mode 100644
index 0000000000..c2adfc0284
--- /dev/null
+++ b/lib/symfony/validator/Command/DebugCommand.php
@@ -0,0 +1,253 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Command;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Dumper;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
+use Symfony\Component\Validator\Mapping\CascadingStrategy;
+use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
+use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
+use Symfony\Component\Validator\Mapping\GenericMetadata;
+use Symfony\Component\Validator\Mapping\TraversalStrategy;
+
+/**
+ * A console command to debug Validators information.
+ *
+ * @author Loïc Frémont
+ */
+#[AsCommand(name: 'debug:validator', description: 'Display validation constraints for classes')]
+class DebugCommand extends Command
+{
+ private MetadataFactoryInterface $validator;
+
+ public function __construct(MetadataFactoryInterface $validator)
+ {
+ parent::__construct();
+
+ $this->validator = $validator;
+ }
+
+ /**
+ * @return void
+ */
+ protected function configure()
+ {
+ $this
+ ->addArgument('class', InputArgument::REQUIRED, 'A fully qualified class name or a path')
+ ->addOption('show-all', null, InputOption::VALUE_NONE, 'Show all classes even if they have no validation constraints')
+ ->setHelp(<<<'EOF'
+The %command.name% 'App\Entity\Dummy' command dumps the validators for the dummy class.
+
+The %command.name% src/ command dumps the validators for the `src` directory.
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $class = $input->getArgument('class');
+
+ if (class_exists($class)) {
+ $this->dumpValidatorsForClass($input, $output, $class);
+
+ return 0;
+ }
+
+ try {
+ foreach ($this->getResourcesByPath($class) as $class) {
+ $this->dumpValidatorsForClass($input, $output, $class);
+ }
+ } catch (DirectoryNotFoundException) {
+ $io = new SymfonyStyle($input, $output);
+ $io->error(\sprintf('Neither class nor path were found with "%s" argument.', $input->getArgument('class')));
+
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private function dumpValidatorsForClass(InputInterface $input, OutputInterface $output, string $class): void
+ {
+ $io = new SymfonyStyle($input, $output);
+ $title = \sprintf('%s', $class);
+ $rows = [];
+ $dump = new Dumper($output);
+
+ /** @var ClassMetadataInterface $classMetadata */
+ $classMetadata = $this->validator->getMetadataFor($class);
+
+ foreach ($this->getClassConstraintsData($classMetadata) as $data) {
+ $rows[] = [
+ '-',
+ $data['class'],
+ implode(', ', $data['groups']),
+ $dump($data['options']),
+ ];
+ }
+
+ foreach ($this->getConstrainedPropertiesData($classMetadata) as $propertyName => $constraintsData) {
+ foreach ($constraintsData as $data) {
+ $rows[] = [
+ $propertyName,
+ $data['class'],
+ implode(', ', $data['groups']),
+ $dump($data['options']),
+ ];
+ }
+ }
+
+ if (!$rows) {
+ if (false === $input->getOption('show-all')) {
+ return;
+ }
+
+ $io->section($title);
+ $io->text('No validators were found for this class.');
+
+ return;
+ }
+
+ $io->section($title);
+
+ $table = new Table($output);
+ $table->setHeaders(['Property', 'Name', 'Groups', 'Options']);
+ $table->setRows($rows);
+ $table->setColumnMaxWidth(3, 80);
+ $table->render();
+ }
+
+ private function getClassConstraintsData(ClassMetadataInterface $classMetadata): iterable
+ {
+ foreach ($classMetadata->getConstraints() as $constraint) {
+ yield [
+ 'class' => $constraint::class,
+ 'groups' => $constraint->groups,
+ 'options' => $this->getConstraintOptions($constraint),
+ ];
+ }
+ }
+
+ private function getConstrainedPropertiesData(ClassMetadataInterface $classMetadata): array
+ {
+ $data = [];
+
+ foreach ($classMetadata->getConstrainedProperties() as $constrainedProperty) {
+ $data[$constrainedProperty] = $this->getPropertyData($classMetadata, $constrainedProperty);
+ }
+
+ return $data;
+ }
+
+ private function getPropertyData(ClassMetadataInterface $classMetadata, string $constrainedProperty): array
+ {
+ $data = [];
+
+ $propertyMetadata = $classMetadata->getPropertyMetadata($constrainedProperty);
+ foreach ($propertyMetadata as $metadata) {
+ $autoMapingStrategy = 'Not supported';
+ if ($metadata instanceof GenericMetadata) {
+ $autoMapingStrategy = match ($metadata->getAutoMappingStrategy()) {
+ AutoMappingStrategy::ENABLED => 'Enabled',
+ AutoMappingStrategy::DISABLED => 'Disabled',
+ AutoMappingStrategy::NONE => 'None',
+ };
+ }
+ $traversalStrategy = 'None';
+ if (TraversalStrategy::TRAVERSE === $metadata->getTraversalStrategy()) {
+ $traversalStrategy = 'Traverse';
+ }
+ if (TraversalStrategy::IMPLICIT === $metadata->getTraversalStrategy()) {
+ $traversalStrategy = 'Implicit';
+ }
+
+ $data[] = [
+ 'class' => 'property options',
+ 'groups' => [],
+ 'options' => [
+ 'cascadeStrategy' => CascadingStrategy::CASCADE === $metadata->getCascadingStrategy() ? 'Cascade' : 'None',
+ 'autoMappingStrategy' => $autoMapingStrategy,
+ 'traversalStrategy' => $traversalStrategy,
+ ],
+ ];
+ foreach ($metadata->getConstraints() as $constraint) {
+ $data[] = [
+ 'class' => $constraint::class,
+ 'groups' => $constraint->groups,
+ 'options' => $this->getConstraintOptions($constraint),
+ ];
+ }
+ }
+
+ return $data;
+ }
+
+ private function getConstraintOptions(Constraint $constraint): array
+ {
+ $options = [];
+
+ foreach (array_keys(get_object_vars($constraint)) as $propertyName) {
+ // Groups are dumped on a specific column.
+ if ('groups' === $propertyName) {
+ continue;
+ }
+
+ $options[$propertyName] = $constraint->$propertyName;
+ }
+
+ ksort($options);
+
+ return $options;
+ }
+
+ private function getResourcesByPath(string $path): array
+ {
+ $finder = new Finder();
+ $finder->files()->in($path)->name('*.php')->sortByName(true);
+ $classes = [];
+
+ foreach ($finder as $file) {
+ $fileContent = file_get_contents($file->getRealPath());
+
+ preg_match('/namespace (.+);/', $fileContent, $matches);
+
+ $namespace = $matches[1] ?? null;
+
+ if (!preg_match('/class +([^{ ]+)/', $fileContent, $matches)) {
+ // no class found
+ continue;
+ }
+
+ $className = trim($matches[1]);
+
+ if (null !== $namespace) {
+ $classes[] = $namespace.'\\'.$className;
+ } else {
+ $classes[] = $className;
+ }
+ }
+
+ return $classes;
+ }
+}
diff --git a/lib/symfony/validator/Constraint.php b/lib/symfony/validator/Constraint.php
new file mode 100644
index 0000000000..2a01a1d7c9
--- /dev/null
+++ b/lib/symfony/validator/Constraint.php
@@ -0,0 +1,321 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+use Symfony\Component\Validator\Exception\InvalidOptionsException;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
+
+/**
+ * Contains the properties of a constraint definition.
+ *
+ * A constraint can be defined on a class, a property or a getter method.
+ * The Constraint class encapsulates all the configuration required for
+ * validating this class, property or getter result successfully.
+ *
+ * Constraint instances are immutable and serializable.
+ *
+ * @author Bernhard Schussek
+ */
+abstract class Constraint
+{
+ /**
+ * The name of the group given to all constraints with no explicit group.
+ */
+ public const DEFAULT_GROUP = 'Default';
+
+ /**
+ * Marks a constraint that can be put onto classes.
+ */
+ public const CLASS_CONSTRAINT = 'class';
+
+ /**
+ * Marks a constraint that can be put onto properties.
+ */
+ public const PROPERTY_CONSTRAINT = 'property';
+
+ /**
+ * Maps error codes to the names of their constants.
+ *
+ * @var array
+ */
+ protected const ERROR_NAMES = [];
+
+ /**
+ * @deprecated since Symfony 6.1, use protected const ERROR_NAMES instead
+ */
+ protected static $errorNames = [];
+
+ /**
+ * Domain-specific data attached to a constraint.
+ *
+ * @var mixed
+ */
+ public $payload;
+
+ /**
+ * The groups that the constraint belongs to.
+ *
+ * @var string[]
+ */
+ public $groups;
+
+ /**
+ * Returns the name of the given error code.
+ *
+ * @throws InvalidArgumentException If the error code does not exist
+ */
+ public static function getErrorName(string $errorCode): string
+ {
+ if (isset(static::ERROR_NAMES[$errorCode])) {
+ return static::ERROR_NAMES[$errorCode];
+ }
+
+ if (!isset(static::$errorNames[$errorCode])) {
+ throw new InvalidArgumentException(\sprintf('The error code "%s" does not exist for constraint of type "%s".', $errorCode, static::class));
+ }
+
+ trigger_deprecation('symfony/validator', '6.1', 'The "%s::$errorNames" property is deprecated, use protected const ERROR_NAMES instead.', static::class);
+
+ return static::$errorNames[$errorCode];
+ }
+
+ /**
+ * Initializes the constraint with options.
+ *
+ * You should pass an associative array. The keys should be the names of
+ * existing properties in this class. The values should be the value for these
+ * properties.
+ *
+ * Alternatively you can override the method getDefaultOption() to return the
+ * name of an existing property. If no associative array is passed, this
+ * property is set instead.
+ *
+ * You can force that certain options are set by overriding
+ * getRequiredOptions() to return the names of these options. If any
+ * option is not set here, an exception is thrown.
+ *
+ * @param mixed $options The options (as associative array)
+ * or the value for the default
+ * option (any other type)
+ * @param string[] $groups An array of validation groups
+ * @param mixed $payload Domain-specific data attached to a constraint
+ *
+ * @throws InvalidOptionsException When you pass the names of non-existing
+ * options
+ * @throws MissingOptionsException When you don't pass any of the options
+ * returned by getRequiredOptions()
+ * @throws ConstraintDefinitionException When you don't pass an associative
+ * array, but getDefaultOption() returns
+ * null
+ */
+ public function __construct(mixed $options = null, ?array $groups = null, mixed $payload = null)
+ {
+ unset($this->groups); // enable lazy initialization
+
+ $options = $this->normalizeOptions($options);
+ if (null !== $groups) {
+ $options['groups'] = $groups;
+ }
+ $options['payload'] = $payload ?? $options['payload'] ?? null;
+
+ foreach ($options as $name => $value) {
+ $this->$name = $value;
+ }
+ }
+
+ protected function normalizeOptions(mixed $options): array
+ {
+ $normalizedOptions = [];
+ $defaultOption = $this->getDefaultOption();
+ $invalidOptions = [];
+ $missingOptions = array_flip((array) $this->getRequiredOptions());
+ $knownOptions = get_class_vars(static::class);
+
+ if (\is_array($options) && isset($options['value']) && !property_exists($this, 'value')) {
+ if (null === $defaultOption) {
+ throw new ConstraintDefinitionException(\sprintf('No default option is configured for constraint "%s".', static::class));
+ }
+
+ $options[$defaultOption] = $options['value'];
+ unset($options['value']);
+ }
+
+ if (\is_array($options)) {
+ reset($options);
+ }
+ if ($options && \is_array($options) && \is_string(key($options))) {
+ foreach ($options as $option => $value) {
+ if (\array_key_exists($option, $knownOptions)) {
+ $normalizedOptions[$option] = $value;
+ unset($missingOptions[$option]);
+ } else {
+ $invalidOptions[] = $option;
+ }
+ }
+ } elseif (null !== $options && !(\is_array($options) && 0 === \count($options))) {
+ if (null === $defaultOption) {
+ throw new ConstraintDefinitionException(\sprintf('No default option is configured for constraint "%s".', static::class));
+ }
+
+ if (\array_key_exists($defaultOption, $knownOptions)) {
+ $normalizedOptions[$defaultOption] = $options;
+ unset($missingOptions[$defaultOption]);
+ } else {
+ $invalidOptions[] = $defaultOption;
+ }
+ }
+
+ if (\count($invalidOptions) > 0) {
+ throw new InvalidOptionsException(\sprintf('The options "%s" do not exist in constraint "%s".', implode('", "', $invalidOptions), static::class), $invalidOptions);
+ }
+
+ if (\count($missingOptions) > 0) {
+ throw new MissingOptionsException(\sprintf('The options "%s" must be set for constraint "%s".', implode('", "', array_keys($missingOptions)), static::class), array_keys($missingOptions));
+ }
+
+ return $normalizedOptions;
+ }
+
+ /**
+ * Sets the value of a lazily initialized option.
+ *
+ * Corresponding properties are added to the object on first access. Hence
+ * this method will be called at most once per constraint instance and
+ * option name.
+ *
+ * @return void
+ *
+ * @throws InvalidOptionsException If an invalid option name is given
+ */
+ public function __set(string $option, mixed $value)
+ {
+ if ('groups' === $option) {
+ $this->groups = (array) $value;
+
+ return;
+ }
+
+ throw new InvalidOptionsException(\sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]);
+ }
+
+ /**
+ * Returns the value of a lazily initialized option.
+ *
+ * Corresponding properties are added to the object on first access. Hence
+ * this method will be called at most once per constraint instance and
+ * option name.
+ *
+ * @throws InvalidOptionsException If an invalid option name is given
+ */
+ public function __get(string $option): mixed
+ {
+ if ('groups' === $option) {
+ $this->groups = [self::DEFAULT_GROUP];
+
+ return $this->groups;
+ }
+
+ throw new InvalidOptionsException(\sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]);
+ }
+
+ public function __isset(string $option): bool
+ {
+ return 'groups' === $option;
+ }
+
+ /**
+ * Adds the given group if this constraint is in the Default group.
+ *
+ * @return void
+ */
+ public function addImplicitGroupName(string $group)
+ {
+ if (null === $this->groups && \array_key_exists('groups', (array) $this)) {
+ throw new \LogicException(\sprintf('"%s::$groups" is set to null. Did you forget to call "%s::__construct()"?', static::class, self::class));
+ }
+
+ if (\in_array(self::DEFAULT_GROUP, $this->groups) && !\in_array($group, $this->groups)) {
+ $this->groups[] = $group;
+ }
+ }
+
+ /**
+ * Returns the name of the default option.
+ *
+ * Override this method to define a default option.
+ *
+ * @return string|null
+ *
+ * @see __construct()
+ */
+ public function getDefaultOption()
+ {
+ return null;
+ }
+
+ /**
+ * Returns the name of the required options.
+ *
+ * Override this method if you want to define required options.
+ *
+ * @return string[]
+ *
+ * @see __construct()
+ */
+ public function getRequiredOptions()
+ {
+ return [];
+ }
+
+ /**
+ * Returns the name of the class that validates this constraint.
+ *
+ * By default, this is the fully qualified name of the constraint class
+ * suffixed with "Validator". You can override this method to change that
+ * behavior.
+ *
+ * @return string
+ */
+ public function validatedBy()
+ {
+ return static::class.'Validator';
+ }
+
+ /**
+ * Returns whether the constraint can be put onto classes, properties or
+ * both.
+ *
+ * This method should return one or more of the constants
+ * Constraint::CLASS_CONSTRAINT and Constraint::PROPERTY_CONSTRAINT.
+ *
+ * @return string|string[] One or more constant values
+ */
+ public function getTargets()
+ {
+ return self::PROPERTY_CONSTRAINT;
+ }
+
+ /**
+ * Optimizes the serialized value to minimize storage space.
+ *
+ * @internal
+ */
+ public function __sleep(): array
+ {
+ // Initialize "groups" option if it is not set
+ $this->groups;
+
+ return array_keys(get_object_vars($this));
+ }
+}
diff --git a/lib/symfony/validator/ConstraintValidator.php b/lib/symfony/validator/ConstraintValidator.php
new file mode 100644
index 0000000000..60808258c3
--- /dev/null
+++ b/lib/symfony/validator/ConstraintValidator.php
@@ -0,0 +1,157 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+/**
+ * Base class for constraint validators.
+ *
+ * @author Bernhard Schussek
+ */
+abstract class ConstraintValidator implements ConstraintValidatorInterface
+{
+ /**
+ * Whether to format {@link \DateTime} objects, either with the {@link \IntlDateFormatter}
+ * (if it is available) or as RFC-3339 dates ("Y-m-d H:i:s").
+ */
+ public const PRETTY_DATE = 1;
+
+ /**
+ * Whether to cast objects with a "__toString()" method to strings.
+ */
+ public const OBJECT_TO_STRING = 2;
+
+ /**
+ * @var ExecutionContextInterface
+ */
+ protected $context;
+
+ /**
+ * @return void
+ */
+ public function initialize(ExecutionContextInterface $context)
+ {
+ $this->context = $context;
+ }
+
+ /**
+ * Returns a string representation of the type of the value.
+ *
+ * This method should be used if you pass the type of a value as
+ * message parameter to a constraint violation. Note that such
+ * parameters should usually not be included in messages aimed at
+ * non-technical people.
+ */
+ protected function formatTypeOf(mixed $value): string
+ {
+ return get_debug_type($value);
+ }
+
+ /**
+ * Returns a string representation of the value.
+ *
+ * This method returns the equivalent PHP tokens for most scalar types
+ * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped
+ * in double quotes ("). Objects, arrays and resources are formatted as
+ * "object", "array" and "resource". If the $format bitmask contains
+ * the PRETTY_DATE bit, then {@link \DateTime} objects will be formatted
+ * with the {@link \IntlDateFormatter}. If it is not available, they will be
+ * formatted as RFC-3339 dates ("Y-m-d H:i:s").
+ *
+ * Be careful when passing message parameters to a constraint violation
+ * that (may) contain objects, arrays or resources. These parameters
+ * should only be displayed for technical users. Non-technical users
+ * won't know what an "object", "array" or "resource" is and will be
+ * confused by the violation message.
+ *
+ * @param int $format A bitwise combination of the format constants in this class
+ */
+ protected function formatValue(mixed $value, int $format = 0): string
+ {
+ if (($format & self::PRETTY_DATE) && $value instanceof \DateTimeInterface) {
+ if (class_exists(\IntlDateFormatter::class)) {
+ $formatter = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, 'UTC');
+
+ return $formatter->format(new \DateTimeImmutable(
+ $value->format('Y-m-d H:i:s.u'),
+ new \DateTimeZone('UTC')
+ ));
+ }
+
+ return $value->format('Y-m-d H:i:s');
+ }
+
+ if ($value instanceof \UnitEnum) {
+ return $value->name;
+ }
+
+ if (\is_object($value)) {
+ if (($format & self::OBJECT_TO_STRING) && $value instanceof \Stringable) {
+ return $value->__toString();
+ }
+
+ return 'object';
+ }
+
+ if (\is_array($value)) {
+ return 'array';
+ }
+
+ if (\is_string($value)) {
+ return '"'.$value.'"';
+ }
+
+ if (\is_resource($value)) {
+ return 'resource';
+ }
+
+ if (null === $value) {
+ return 'null';
+ }
+
+ if (false === $value) {
+ return 'false';
+ }
+
+ if (true === $value) {
+ return 'true';
+ }
+
+ if (is_nan($value)) {
+ return 'NAN';
+ }
+
+ return (string) $value;
+ }
+
+ /**
+ * Returns a string representation of a list of values.
+ *
+ * Each of the values is converted to a string using
+ * {@link formatValue()}. The values are then concatenated with commas.
+ *
+ * @param array $values A list of values
+ * @param int $format A bitwise combination of the format
+ * constants in this class
+ *
+ * @see formatValue()
+ */
+ protected function formatValues(array $values, int $format = 0): string
+ {
+ foreach ($values as $key => $value) {
+ $values[$key] = $this->formatValue($value, $format);
+ }
+
+ return implode(', ', $values);
+ }
+}
diff --git a/lib/symfony/validator/ConstraintValidatorFactory.php b/lib/symfony/validator/ConstraintValidatorFactory.php
new file mode 100644
index 0000000000..778e202a84
--- /dev/null
+++ b/lib/symfony/validator/ConstraintValidatorFactory.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+use Symfony\Component\Validator\Constraints\ExpressionValidator;
+
+/**
+ * Default implementation of the ConstraintValidatorFactoryInterface.
+ *
+ * This enforces the convention that the validatedBy() method on any
+ * Constraint will return the class name of the ConstraintValidator that
+ * should validate the Constraint.
+ *
+ * @author Bernhard Schussek
+ */
+class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
+{
+ protected $validators = [];
+
+ public function __construct(array $validators = [])
+ {
+ $this->validators = $validators;
+ }
+
+ public function getInstance(Constraint $constraint): ConstraintValidatorInterface
+ {
+ if ('validator.expression' === $name = $class = $constraint->validatedBy()) {
+ $class = ExpressionValidator::class;
+ }
+
+ return $this->validators[$name] ??= new $class();
+ }
+}
diff --git a/lib/symfony/validator/ConstraintValidatorFactoryInterface.php b/lib/symfony/validator/ConstraintValidatorFactoryInterface.php
new file mode 100644
index 0000000000..f0abd27191
--- /dev/null
+++ b/lib/symfony/validator/ConstraintValidatorFactoryInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+/**
+ * Specifies an object able to return the correct ConstraintValidatorInterface
+ * instance given a Constraint object.
+ */
+interface ConstraintValidatorFactoryInterface
+{
+ /**
+ * Given a Constraint, this returns the ConstraintValidatorInterface
+ * object that should be used to verify its validity.
+ */
+ public function getInstance(Constraint $constraint): ConstraintValidatorInterface;
+}
diff --git a/lib/symfony/validator/ConstraintValidatorInterface.php b/lib/symfony/validator/ConstraintValidatorInterface.php
new file mode 100644
index 0000000000..fe7da2e8f7
--- /dev/null
+++ b/lib/symfony/validator/ConstraintValidatorInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+/**
+ * @author Bernhard Schussek
+ */
+interface ConstraintValidatorInterface
+{
+ /**
+ * Initializes the constraint validator.
+ *
+ * @return void
+ */
+ public function initialize(ExecutionContextInterface $context);
+
+ /**
+ * Checks if the passed value is valid.
+ *
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint);
+}
diff --git a/lib/symfony/validator/ConstraintViolation.php b/lib/symfony/validator/ConstraintViolation.php
new file mode 100644
index 0000000000..5129ccf952
--- /dev/null
+++ b/lib/symfony/validator/ConstraintViolation.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+/**
+ * Default implementation of {@ConstraintViolationInterface}.
+ *
+ * @author Bernhard Schussek
+ */
+class ConstraintViolation implements ConstraintViolationInterface
+{
+ private string|\Stringable $message;
+ private ?string $messageTemplate;
+ private array $parameters;
+ private ?int $plural;
+ private mixed $root;
+ private ?string $propertyPath;
+ private mixed $invalidValue;
+ private ?Constraint $constraint;
+ private ?string $code;
+ private mixed $cause;
+
+ /**
+ * Creates a new constraint violation.
+ *
+ * @param string|\Stringable $message The violation message as a string or a stringable object
+ * @param string|null $messageTemplate The raw violation message
+ * @param array $parameters The parameters to substitute in the
+ * raw violation message
+ * @param mixed $root The value originally passed to the
+ * validator
+ * @param string|null $propertyPath The property path from the root
+ * value to the invalid value
+ * @param mixed $invalidValue The invalid value that caused this
+ * violation
+ * @param int|null $plural The number for determining the plural
+ * form when translating the message
+ * @param string|null $code The error code of the violation
+ * @param Constraint|null $constraint The constraint whose validation
+ * caused the violation
+ * @param mixed $cause The cause of the violation
+ */
+ public function __construct(string|\Stringable $message, ?string $messageTemplate, array $parameters, mixed $root, ?string $propertyPath, mixed $invalidValue, ?int $plural = null, ?string $code = null, ?Constraint $constraint = null, mixed $cause = null)
+ {
+ $this->message = $message;
+ $this->messageTemplate = $messageTemplate;
+ $this->parameters = $parameters;
+ $this->plural = $plural;
+ $this->root = $root;
+ $this->propertyPath = $propertyPath;
+ $this->invalidValue = $invalidValue;
+ $this->constraint = $constraint;
+ $this->code = $code;
+ $this->cause = $cause;
+ }
+
+ public function __toString(): string
+ {
+ if (\is_object($this->root)) {
+ $class = 'Object('.$this->root::class.')';
+ } elseif (\is_array($this->root)) {
+ $class = 'Array';
+ } else {
+ $class = (string) $this->root;
+ }
+
+ $propertyPath = (string) $this->propertyPath;
+
+ if ('' !== $propertyPath && '[' !== $propertyPath[0] && '' !== $class) {
+ $class .= '.';
+ }
+
+ if (null !== ($code = $this->code) && '' !== $code) {
+ $code = ' (code '.$code.')';
+ }
+
+ return $class.$propertyPath.":\n ".$this->getMessage().$code;
+ }
+
+ public function getMessageTemplate(): string
+ {
+ return (string) $this->messageTemplate;
+ }
+
+ public function getParameters(): array
+ {
+ return $this->parameters;
+ }
+
+ public function getPlural(): ?int
+ {
+ return $this->plural;
+ }
+
+ public function getMessage(): string|\Stringable
+ {
+ return $this->message;
+ }
+
+ public function getRoot(): mixed
+ {
+ return $this->root;
+ }
+
+ public function getPropertyPath(): string
+ {
+ return (string) $this->propertyPath;
+ }
+
+ public function getInvalidValue(): mixed
+ {
+ return $this->invalidValue;
+ }
+
+ /**
+ * Returns the constraint whose validation caused the violation.
+ */
+ public function getConstraint(): ?Constraint
+ {
+ return $this->constraint;
+ }
+
+ /**
+ * Returns the cause of the violation.
+ */
+ public function getCause(): mixed
+ {
+ return $this->cause;
+ }
+
+ public function getCode(): ?string
+ {
+ return $this->code;
+ }
+}
diff --git a/lib/symfony/validator/ConstraintViolationInterface.php b/lib/symfony/validator/ConstraintViolationInterface.php
new file mode 100644
index 0000000000..6eb2797406
--- /dev/null
+++ b/lib/symfony/validator/ConstraintViolationInterface.php
@@ -0,0 +1,116 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+/**
+ * A violation of a constraint that happened during validation.
+ *
+ * For each constraint that fails during validation one or more violations are
+ * created. The violations store the violation message, the path to the failing
+ * element in the validation graph and the root element that was originally
+ * passed to the validator. For example, take the following graph:
+ *
+ * (Person)---(firstName: string)
+ * \
+ * (address: Address)---(street: string)
+ *
+ * If the Person object is validated and validation fails for the
+ * "firstName" property, the generated violation has the Person
+ * instance as root and the property path "firstName". If validation fails
+ * for the "street" property of the related Address instance, the root
+ * element is still the person, but the property path is "address.street".
+ *
+ * @author Bernhard Schussek
+ *
+ * @method Constraint|null getConstraint() Returns the constraint whose validation caused the violation. Not implementing it is deprecated since Symfony 6.3.
+ * @method mixed getCause() Returns the cause of the violation. Not implementing it is deprecated since Symfony 6.2.
+ * @method string __toString() Converts the violation into a string for debugging purposes. Not implementing it is deprecated since Symfony 6.1.
+ */
+interface ConstraintViolationInterface
+{
+ /**
+ * Returns the violation message.
+ */
+ public function getMessage(): string|\Stringable;
+
+ /**
+ * Returns the raw violation message.
+ *
+ * The raw violation message contains placeholders for the parameters
+ * returned by {@link getParameters}. Typically you'll pass the
+ * message template and parameters to a translation engine.
+ */
+ public function getMessageTemplate(): string;
+
+ /**
+ * Returns the parameters to be inserted into the raw violation message.
+ *
+ * @return array a possibly empty list of parameters indexed by the names
+ * that appear in the message template
+ *
+ * @see getMessageTemplate()
+ */
+ public function getParameters(): array;
+
+ /**
+ * Returns a number for pluralizing the violation message.
+ *
+ * For example, the message template could have different translation based
+ * on a parameter "choices":
+ *
+ *
+ * - Please select exactly one entry. (choices=1)
+ * - Please select two entries. (choices=2)
+ *
+ *
+ * This method returns the value of the parameter for choosing the right
+ * pluralization form (in this case "choices").
+ */
+ public function getPlural(): ?int;
+
+ /**
+ * Returns the root element of the validation.
+ *
+ * @return mixed The value that was passed originally to the validator when
+ * the validation was started. Because the validator traverses
+ * the object graph, the value at which the violation occurs
+ * is not necessarily the value that was originally validated.
+ */
+ public function getRoot(): mixed;
+
+ /**
+ * Returns the property path from the root element to the violation.
+ *
+ * @return string The property path indicates how the validator reached
+ * the invalid value from the root element. If the root
+ * element is a Person instance with a property
+ * "address" that contains an Address instance
+ * with an invalid property "street", the generated property
+ * path is "address.street". Property access is denoted by
+ * dots, while array access is denoted by square brackets,
+ * for example "addresses[1].street".
+ */
+ public function getPropertyPath(): string;
+
+ /**
+ * Returns the value that caused the violation.
+ *
+ * @return mixed the invalid value that caused the validated constraint to
+ * fail
+ */
+ public function getInvalidValue(): mixed;
+
+ /**
+ * Returns a machine-digestible error code for the violation.
+ */
+ public function getCode(): ?string;
+}
diff --git a/lib/symfony/validator/ConstraintViolationList.php b/lib/symfony/validator/ConstraintViolationList.php
new file mode 100644
index 0000000000..dc2a6df437
--- /dev/null
+++ b/lib/symfony/validator/ConstraintViolationList.php
@@ -0,0 +1,163 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+use Symfony\Component\Validator\Exception\OutOfBoundsException;
+
+/**
+ * Default implementation of {@ConstraintViolationListInterface}.
+ *
+ * @author Bernhard Schussek
+ *
+ * @implements \IteratorAggregate
+ */
+class ConstraintViolationList implements \IteratorAggregate, ConstraintViolationListInterface
+{
+ /**
+ * @var list
+ */
+ private array $violations = [];
+
+ /**
+ * Creates a new constraint violation list.
+ *
+ * @param iterable $violations The constraint violations to add to the list
+ */
+ public function __construct(iterable $violations = [])
+ {
+ foreach ($violations as $violation) {
+ $this->add($violation);
+ }
+ }
+
+ public static function createFromMessage(string $message): self
+ {
+ $self = new self();
+ $self->add(new ConstraintViolation($message, '', [], null, '', null));
+
+ return $self;
+ }
+
+ public function __toString(): string
+ {
+ $string = '';
+
+ foreach ($this->violations as $violation) {
+ $string .= $violation."\n";
+ }
+
+ return $string;
+ }
+
+ /**
+ * @return void
+ */
+ public function add(ConstraintViolationInterface $violation)
+ {
+ $this->violations[] = $violation;
+ }
+
+ /**
+ * @return void
+ */
+ public function addAll(ConstraintViolationListInterface $otherList)
+ {
+ foreach ($otherList as $violation) {
+ $this->violations[] = $violation;
+ }
+ }
+
+ public function get(int $offset): ConstraintViolationInterface
+ {
+ if (!isset($this->violations[$offset])) {
+ throw new OutOfBoundsException(\sprintf('The offset "%s" does not exist.', $offset));
+ }
+
+ return $this->violations[$offset];
+ }
+
+ public function has(int $offset): bool
+ {
+ return isset($this->violations[$offset]);
+ }
+
+ /**
+ * @return void
+ */
+ public function set(int $offset, ConstraintViolationInterface $violation)
+ {
+ $this->violations[$offset] = $violation;
+ }
+
+ /**
+ * @return void
+ */
+ public function remove(int $offset)
+ {
+ unset($this->violations[$offset]);
+ }
+
+ /**
+ * @return \ArrayIterator
+ */
+ public function getIterator(): \ArrayIterator
+ {
+ return new \ArrayIterator($this->violations);
+ }
+
+ public function count(): int
+ {
+ return \count($this->violations);
+ }
+
+ public function offsetExists(mixed $offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ public function offsetGet(mixed $offset): ConstraintViolationInterface
+ {
+ return $this->get($offset);
+ }
+
+ public function offsetSet(mixed $offset, mixed $violation): void
+ {
+ if (null === $offset) {
+ $this->add($violation);
+ } else {
+ $this->set($offset, $violation);
+ }
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ $this->remove($offset);
+ }
+
+ /**
+ * Creates iterator for errors with specific codes.
+ *
+ * @param string|string[] $codes The codes to find
+ */
+ public function findByCodes(string|array $codes): static
+ {
+ $codes = (array) $codes;
+ $violations = [];
+ foreach ($this as $violation) {
+ if (\in_array($violation->getCode(), $codes, true)) {
+ $violations[] = $violation;
+ }
+ }
+
+ return new static($violations);
+ }
+}
diff --git a/lib/symfony/validator/ConstraintViolationListInterface.php b/lib/symfony/validator/ConstraintViolationListInterface.php
new file mode 100644
index 0000000000..1fdbf0bc3f
--- /dev/null
+++ b/lib/symfony/validator/ConstraintViolationListInterface.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+use Symfony\Component\Validator\Exception\OutOfBoundsException;
+
+/**
+ * A list of constraint violations.
+ *
+ * @author Bernhard Schussek
+ *
+ * @extends \ArrayAccess
+ * @extends \Traversable
+ *
+ * @method string __toString() Converts the violation into a string for debugging purposes. Not implementing it is deprecated since Symfony 6.1.
+ */
+interface ConstraintViolationListInterface extends \Traversable, \Countable, \ArrayAccess
+{
+ /**
+ * Adds a constraint violation to this list.
+ *
+ * @return void
+ */
+ public function add(ConstraintViolationInterface $violation);
+
+ /**
+ * Merges an existing violation list into this list.
+ *
+ * @return void
+ */
+ public function addAll(self $otherList);
+
+ /**
+ * Returns the violation at a given offset.
+ *
+ * @param int $offset The offset of the violation
+ *
+ * @throws OutOfBoundsException if the offset does not exist
+ */
+ public function get(int $offset): ConstraintViolationInterface;
+
+ /**
+ * Returns whether the given offset exists.
+ *
+ * @param int $offset The violation offset
+ */
+ public function has(int $offset): bool;
+
+ /**
+ * Sets a violation at a given offset.
+ *
+ * @param int $offset The violation offset
+ *
+ * @return void
+ */
+ public function set(int $offset, ConstraintViolationInterface $violation);
+
+ /**
+ * Removes a violation at a given offset.
+ *
+ * @param int $offset The offset to remove
+ *
+ * @return void
+ */
+ public function remove(int $offset);
+}
diff --git a/lib/symfony/validator/Constraints/AbstractComparison.php b/lib/symfony/validator/Constraints/AbstractComparison.php
new file mode 100644
index 0000000000..ffba055116
--- /dev/null
+++ b/lib/symfony/validator/Constraints/AbstractComparison.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * Used for the comparison of values.
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+abstract class AbstractComparison extends Constraint
+{
+ public $message;
+ public $value;
+ public $propertyPath;
+
+ public function __construct(mixed $value = null, ?string $propertyPath = null, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = [])
+ {
+ if (\is_array($value)) {
+ $options = array_merge($value, $options);
+ } elseif (null !== $value) {
+ $options['value'] = $value;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->propertyPath = $propertyPath ?? $this->propertyPath;
+
+ if (null === $this->value && null === $this->propertyPath) {
+ throw new ConstraintDefinitionException(\sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', static::class));
+ }
+
+ if (null !== $this->value && null !== $this->propertyPath) {
+ throw new ConstraintDefinitionException(\sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', static::class));
+ }
+
+ if (null !== $this->propertyPath && !class_exists(PropertyAccess::class)) {
+ throw new LogicException(\sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option. Try running "composer require symfony/property-access".', static::class));
+ }
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'value';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/AbstractComparisonValidator.php b/lib/symfony/validator/Constraints/AbstractComparisonValidator.php
new file mode 100644
index 0000000000..b7284fdb9e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/AbstractComparisonValidator.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
+use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * Provides a base class for the validation of property comparisons.
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+abstract class AbstractComparisonValidator extends ConstraintValidator
+{
+ private ?PropertyAccessorInterface $propertyAccessor;
+
+ public function __construct(?PropertyAccessorInterface $propertyAccessor = null)
+ {
+ $this->propertyAccessor = $propertyAccessor;
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof AbstractComparison) {
+ throw new UnexpectedTypeException($constraint, AbstractComparison::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ if ($path = $constraint->propertyPath) {
+ if (null === $object = $this->context->getObject()) {
+ return;
+ }
+
+ try {
+ $comparedValue = $this->getPropertyAccessor()->getValue($object, $path);
+ } catch (NoSuchPropertyException $e) {
+ throw new ConstraintDefinitionException(\sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e);
+ } catch (UninitializedPropertyException) {
+ $comparedValue = null;
+ }
+ } else {
+ $comparedValue = $constraint->value;
+ }
+
+ // Convert strings to date-time objects if comparing to another date-time object
+ // This allows to compare with any date/time value supported by date-time constructors:
+ // https://php.net/datetime.formats
+ if (\is_string($comparedValue) && $value instanceof \DateTimeInterface) {
+ try {
+ $comparedValue = new $value($comparedValue);
+ } catch (\Exception) {
+ throw new ConstraintDefinitionException(\sprintf('The compared value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $comparedValue, get_debug_type($value), get_debug_type($constraint)));
+ }
+ }
+
+ if (!$this->compareValues($value, $comparedValue)) {
+ $violationBuilder = $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING | self::PRETTY_DATE))
+ ->setParameter('{{ compared_value }}', $this->formatValue($comparedValue, self::OBJECT_TO_STRING | self::PRETTY_DATE))
+ ->setParameter('{{ compared_value_type }}', $this->formatTypeOf($comparedValue))
+ ->setCode($this->getErrorCode());
+
+ if (null !== $path) {
+ $violationBuilder->setParameter('{{ compared_value_path }}', $path);
+ }
+
+ $violationBuilder->addViolation();
+ }
+ }
+
+ private function getPropertyAccessor(): PropertyAccessorInterface
+ {
+ return $this->propertyAccessor ??= PropertyAccess::createPropertyAccessor();
+ }
+
+ /**
+ * Compares the two given values to find if their relationship is valid.
+ */
+ abstract protected function compareValues(mixed $value1, mixed $value2): bool;
+
+ /**
+ * Returns the error code used if the comparison fails.
+ */
+ protected function getErrorCode(): ?string
+ {
+ return null;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/All.php b/lib/symfony/validator/Constraints/All.php
new file mode 100644
index 0000000000..0888084d77
--- /dev/null
+++ b/lib/symfony/validator/Constraints/All.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class All extends Composite
+{
+ public $constraints = [];
+
+ public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($constraints ?? [], $groups, $payload);
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'constraints';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['constraints'];
+ }
+
+ protected function getCompositeOption(): string
+ {
+ return 'constraints';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/AllValidator.php b/lib/symfony/validator/Constraints/AllValidator.php
new file mode 100644
index 0000000000..15896f29da
--- /dev/null
+++ b/lib/symfony/validator/Constraints/AllValidator.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class AllValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof All) {
+ throw new UnexpectedTypeException($constraint, All::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ if (!\is_array($value) && !$value instanceof \Traversable) {
+ throw new UnexpectedValueException($value, 'iterable');
+ }
+
+ $context = $this->context;
+
+ $validator = $context->getValidator()->inContext($context);
+
+ foreach ($value as $key => $element) {
+ $validator->atPath('['.$key.']')->validate($element, $constraint->constraints);
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/AtLeastOneOf.php b/lib/symfony/validator/Constraints/AtLeastOneOf.php
new file mode 100644
index 0000000000..4e86473974
--- /dev/null
+++ b/lib/symfony/validator/Constraints/AtLeastOneOf.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Przemysław Bogusz
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class AtLeastOneOf extends Composite
+{
+ public const AT_LEAST_ONE_OF_ERROR = 'f27e6d6c-261a-4056-b391-6673a623531c';
+
+ protected const ERROR_NAMES = [
+ self::AT_LEAST_ONE_OF_ERROR => 'AT_LEAST_ONE_OF_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $constraints = [];
+ public $message = 'This value should satisfy at least one of the following constraints:';
+ public $messageCollection = 'Each element of this collection should satisfy its own set of constraints.';
+ public $includeInternalMessages = true;
+
+ public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null, ?string $message = null, ?string $messageCollection = null, ?bool $includeInternalMessages = null)
+ {
+ parent::__construct($constraints ?? [], $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->messageCollection = $messageCollection ?? $this->messageCollection;
+ $this->includeInternalMessages = $includeInternalMessages ?? $this->includeInternalMessages;
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'constraints';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['constraints'];
+ }
+
+ protected function getCompositeOption(): string
+ {
+ return 'constraints';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/AtLeastOneOfValidator.php b/lib/symfony/validator/Constraints/AtLeastOneOfValidator.php
new file mode 100644
index 0000000000..5348527c70
--- /dev/null
+++ b/lib/symfony/validator/Constraints/AtLeastOneOfValidator.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Przemysław Bogusz
+ */
+class AtLeastOneOfValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof AtLeastOneOf) {
+ throw new UnexpectedTypeException($constraint, AtLeastOneOf::class);
+ }
+
+ $validator = $this->context->getValidator();
+
+ // Build a first violation to have the base message of the constraint translated
+ $baseMessageContext = clone $this->context;
+ $baseMessageContext->buildViolation($constraint->message)->addViolation();
+ $baseViolations = $baseMessageContext->getViolations();
+ $messages = [(string) $baseViolations->get(\count($baseViolations) - 1)->getMessage()];
+
+ foreach ($constraint->constraints as $key => $item) {
+ if (!\in_array($this->context->getGroup(), $item->groups, true)) {
+ continue;
+ }
+
+ $context = $this->context;
+ $executionContext = clone $this->context;
+ $executionContext->setNode($value, $this->context->getObject(), $this->context->getMetadata(), $this->context->getPropertyPath());
+ $violations = $validator->inContext($executionContext)->validate($value, $item, $this->context->getGroup())->getViolations();
+ $this->context = $context;
+
+ if (\count($this->context->getViolations()) === \count($violations)) {
+ return;
+ }
+
+ if ($constraint->includeInternalMessages) {
+ $message = ' ['.($key + 1).'] ';
+
+ if ($item instanceof All || $item instanceof Collection) {
+ $message .= $constraint->messageCollection;
+ } else {
+ $message .= $violations->get(\count($violations) - 1)->getMessage();
+ }
+
+ $messages[] = $message;
+ }
+ }
+
+ $this->context->buildViolation(implode('', $messages))
+ ->setCode(AtLeastOneOf::AT_LEAST_ONE_OF_ERROR)
+ ->addViolation()
+ ;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Bic.php b/lib/symfony/validator/Constraints/Bic.php
new file mode 100644
index 0000000000..dfd54f7a2d
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Bic.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Countries;
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Michael Hirschler
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Bic extends Constraint
+{
+ public const INVALID_LENGTH_ERROR = '66dad313-af0b-4214-8566-6c799be9789c';
+ public const INVALID_CHARACTERS_ERROR = 'f424c529-7add-4417-8f2d-4b656e4833e2';
+ public const INVALID_BANK_CODE_ERROR = '00559357-6170-4f29-aebd-d19330aa19cf';
+ public const INVALID_COUNTRY_CODE_ERROR = '1ce76f8d-3c1f-451c-9e62-fe9c3ed486ae';
+ public const INVALID_CASE_ERROR = '11884038-3312-4ae5-9d04-699f782130c7';
+ public const INVALID_IBAN_COUNTRY_CODE_ERROR = '29a2c3bb-587b-4996-b6f5-53081364cea5';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR',
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::INVALID_BANK_CODE_ERROR => 'INVALID_BANK_CODE_ERROR',
+ self::INVALID_COUNTRY_CODE_ERROR => 'INVALID_COUNTRY_CODE_ERROR',
+ self::INVALID_CASE_ERROR => 'INVALID_CASE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This is not a valid Business Identifier Code (BIC).';
+ public $ibanMessage = 'This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.';
+ public $iban;
+ public $ibanPropertyPath;
+
+ public function __construct(?array $options = null, ?string $message = null, ?string $iban = null, ?string $ibanPropertyPath = null, ?string $ibanMessage = null, ?array $groups = null, mixed $payload = null)
+ {
+ if (!class_exists(Countries::class)) {
+ throw new LogicException('The Intl component is required to use the Bic constraint. Try running "composer require symfony/intl".');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->ibanMessage = $ibanMessage ?? $this->ibanMessage;
+ $this->iban = $iban ?? $this->iban;
+ $this->ibanPropertyPath = $ibanPropertyPath ?? $this->ibanPropertyPath;
+
+ if (null !== $this->iban && null !== $this->ibanPropertyPath) {
+ throw new ConstraintDefinitionException('The "iban" and "ibanPropertyPath" options of the Iban constraint cannot be used at the same time.');
+ }
+
+ if (null !== $this->ibanPropertyPath && !class_exists(PropertyAccess::class)) {
+ throw new LogicException(\sprintf('The "symfony/property-access" component is required to use the "%s" constraint with the "ibanPropertyPath" option. Try running "composer require symfony/property-access".', self::class));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/BicValidator.php b/lib/symfony/validator/Constraints/BicValidator.php
new file mode 100644
index 0000000000..d038b8cb82
--- /dev/null
+++ b/lib/symfony/validator/Constraints/BicValidator.php
@@ -0,0 +1,167 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Countries;
+use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
+use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\PropertyAccess\PropertyAccessor;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Michael Hirschler
+ *
+ * @see https://en.wikipedia.org/wiki/ISO_9362#Structure
+ */
+class BicValidator extends ConstraintValidator
+{
+ // Reference: https://www.iban.com/structure
+ private const BIC_COUNTRY_TO_IBAN_COUNTRY_MAP = [
+ // FR includes:
+ 'GF' => 'FR', // French Guiana
+ 'PF' => 'FR', // French Polynesia
+ 'TF' => 'FR', // French Southern Territories
+ 'GP' => 'FR', // Guadeloupe
+ 'MQ' => 'FR', // Martinique
+ 'YT' => 'FR', // Mayotte
+ 'NC' => 'FR', // New Caledonia
+ 'RE' => 'FR', // Reunion
+ 'BL' => 'FR', // Saint Barthelemy
+ 'MF' => 'FR', // Saint Martin (French part)
+ 'PM' => 'FR', // Saint Pierre and Miquelon
+ 'WF' => 'FR', // Wallis and Futuna Islands
+ // GB includes:
+ 'JE' => 'GB', // Jersey
+ 'IM' => 'GB', // Isle of Man
+ 'GG' => 'GB', // Guernsey
+ 'VG' => 'GB', // British Virgin Islands
+ // FI includes:
+ 'AX' => 'FI', // Aland Islands
+ // ES includes:
+ 'IC' => 'ES', // Canary Islands
+ 'EA' => 'ES', // Ceuta and Melilla
+ ];
+
+ private ?PropertyAccessor $propertyAccessor;
+
+ public function __construct(?PropertyAccessor $propertyAccessor = null)
+ {
+ $this->propertyAccessor = $propertyAccessor;
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Bic) {
+ throw new UnexpectedTypeException($constraint, Bic::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $canonicalize = str_replace(' ', '', $value);
+
+ // the bic must be either 8 or 11 characters long
+ if (!\in_array(\strlen($canonicalize), [8, 11])) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Bic::INVALID_LENGTH_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // must contain alphanumeric values only
+ if (!ctype_alnum($canonicalize)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Bic::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $bicCountryCode = substr($canonicalize, 4, 2);
+ if (!isset(self::BIC_COUNTRY_TO_IBAN_COUNTRY_MAP[$bicCountryCode]) && !Countries::exists($bicCountryCode)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Bic::INVALID_COUNTRY_CODE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // should contain uppercase characters only
+ if (strtoupper($canonicalize) !== $canonicalize) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Bic::INVALID_CASE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // check against an IBAN
+ $iban = $constraint->iban;
+ $path = $constraint->ibanPropertyPath;
+ if ($path && null !== $object = $this->context->getObject()) {
+ try {
+ $iban = $this->getPropertyAccessor()->getValue($object, $path);
+ } catch (NoSuchPropertyException $e) {
+ throw new ConstraintDefinitionException(\sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e);
+ } catch (UninitializedPropertyException) {
+ $iban = null;
+ }
+ }
+ if (!$iban) {
+ return;
+ }
+ $ibanCountryCode = substr($iban, 0, 2);
+ if (ctype_alpha($ibanCountryCode) && !$this->bicAndIbanCountriesMatch($bicCountryCode, $ibanCountryCode)) {
+ $this->context->buildViolation($constraint->ibanMessage)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setParameter('{{ iban }}', $iban)
+ ->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR)
+ ->addViolation();
+ }
+ }
+
+ private function getPropertyAccessor(): PropertyAccessor
+ {
+ if (null === $this->propertyAccessor) {
+ if (!class_exists(PropertyAccess::class)) {
+ throw new LogicException('Unable to use property path as the Symfony PropertyAccess component is not installed. Try running "composer require symfony/property-access".');
+ }
+ $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
+ }
+
+ return $this->propertyAccessor;
+ }
+
+ private function bicAndIbanCountriesMatch(string $bicCountryCode, string $ibanCountryCode): bool
+ {
+ return $ibanCountryCode === $bicCountryCode || $ibanCountryCode === (self::BIC_COUNTRY_TO_IBAN_COUNTRY_MAP[$bicCountryCode] ?? null);
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Blank.php b/lib/symfony/validator/Constraints/Blank.php
new file mode 100644
index 0000000000..00a4e65d97
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Blank.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Blank extends Constraint
+{
+ public const NOT_BLANK_ERROR = '183ad2de-533d-4796-a439-6d3c3852b549';
+
+ protected const ERROR_NAMES = [
+ self::NOT_BLANK_ERROR => 'NOT_BLANK_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be blank.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options ?? [], $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/BlankValidator.php b/lib/symfony/validator/Constraints/BlankValidator.php
new file mode 100644
index 0000000000..2551d5e244
--- /dev/null
+++ b/lib/symfony/validator/Constraints/BlankValidator.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class BlankValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Blank) {
+ throw new UnexpectedTypeException($constraint, Blank::class);
+ }
+
+ if ('' !== $value && null !== $value) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Blank::NOT_BLANK_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Callback.php b/lib/symfony/validator/Constraints/Callback.php
new file mode 100644
index 0000000000..5cff153d6b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Callback.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Callback extends Constraint
+{
+ /**
+ * @var string|callable
+ */
+ public $callback;
+
+ public function __construct(array|string|callable|null $callback = null, ?array $groups = null, mixed $payload = null, array $options = [])
+ {
+ // Invocation through annotations with an array parameter only
+ if (\is_array($callback) && 1 === \count($callback) && isset($callback['value'])) {
+ $callback = $callback['value'];
+ }
+
+ if (!\is_array($callback) || (!isset($callback['callback']) && !isset($callback['groups']) && !isset($callback['payload']))) {
+ $options['callback'] = $callback;
+ } else {
+ $options = array_merge($callback, $options);
+ }
+
+ parent::__construct($options, $groups, $payload);
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'callback';
+ }
+
+ public function getTargets(): string|array
+ {
+ return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CallbackValidator.php b/lib/symfony/validator/Constraints/CallbackValidator.php
new file mode 100644
index 0000000000..e1936ad966
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CallbackValidator.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * Validator for Callback constraint.
+ *
+ * @author Bernhard Schussek
+ */
+class CallbackValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $object, Constraint $constraint)
+ {
+ if (!$constraint instanceof Callback) {
+ throw new UnexpectedTypeException($constraint, Callback::class);
+ }
+
+ $method = $constraint->callback;
+ if ($method instanceof \Closure) {
+ $method($object, $this->context, $constraint->payload);
+ } elseif (\is_array($method)) {
+ if (!\is_callable($method)) {
+ if (isset($method[0]) && \is_object($method[0])) {
+ $method[0] = $method[0]::class;
+ }
+ throw new ConstraintDefinitionException(json_encode($method).' targeted by Callback constraint is not a valid callable.');
+ }
+
+ $method($object, $this->context, $constraint->payload);
+ } elseif (null !== $object) {
+ if (!method_exists($object, $method)) {
+ throw new ConstraintDefinitionException(\sprintf('Method "%s" targeted by Callback constraint does not exist in class "%s".', $method, get_debug_type($object)));
+ }
+
+ $reflMethod = new \ReflectionMethod($object, $method);
+
+ if ($reflMethod->isStatic()) {
+ $reflMethod->invoke(null, $object, $this->context, $constraint->payload);
+ } else {
+ $reflMethod->invoke($object, $this->context, $constraint->payload);
+ }
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CardScheme.php b/lib/symfony/validator/Constraints/CardScheme.php
new file mode 100644
index 0000000000..39eb1cd59b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CardScheme.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Metadata for the CardSchemeValidator.
+ *
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Tim Nagel
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class CardScheme extends Constraint
+{
+ public const AMEX = 'AMEX';
+ public const CHINA_UNIONPAY = 'CHINA_UNIONPAY';
+ public const DINERS = 'DINERS';
+ public const DISCOVER = 'DISCOVER';
+ public const INSTAPAYMENT = 'INSTAPAYMENT';
+ public const JCB = 'JCB';
+ public const LASER = 'LASER';
+ public const MAESTRO = 'MAESTRO';
+ public const MASTERCARD = 'MASTERCARD';
+ public const MIR = 'MIR';
+ public const UATP = 'UATP';
+ public const VISA = 'VISA';
+
+ public const NOT_NUMERIC_ERROR = 'a2ad9231-e827-485f-8a1e-ef4d9a6d5c2e';
+ public const INVALID_FORMAT_ERROR = 'a8faedbf-1c2f-4695-8d22-55783be8efed';
+
+ protected const ERROR_NAMES = [
+ self::NOT_NUMERIC_ERROR => 'NOT_NUMERIC_ERROR',
+ self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'Unsupported card type or invalid card number.';
+ public $schemes;
+
+ public function __construct(array|string|null $schemes, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = [])
+ {
+ if (\is_array($schemes) && \is_string(key($schemes))) {
+ $options = array_merge($schemes, $options);
+ } elseif (null !== $schemes) {
+ $options['value'] = $schemes;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'schemes';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['schemes'];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CardSchemeValidator.php b/lib/symfony/validator/Constraints/CardSchemeValidator.php
new file mode 100644
index 0000000000..22de0d8db7
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CardSchemeValidator.php
@@ -0,0 +1,134 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * Validates that a card number belongs to a specified scheme.
+ *
+ * @author Tim Nagel
+ * @author Bernhard Schussek
+ *
+ * @see https://en.wikipedia.org/wiki/Payment_card_number
+ * @see https://www.regular-expressions.info/creditcard.html
+ */
+class CardSchemeValidator extends ConstraintValidator
+{
+ protected $schemes = [
+ // American Express card numbers start with 34 or 37 and have 15 digits.
+ CardScheme::AMEX => [
+ '/^3[47][0-9]{13}$/D',
+ ],
+ // China UnionPay cards start with 62 and have between 16 and 19 digits.
+ // Please note that these cards do not follow Luhn Algorithm as a checksum.
+ CardScheme::CHINA_UNIONPAY => [
+ '/^62[0-9]{14,17}$/D',
+ ],
+ // Diners Club card numbers begin with 300 through 305, 36 or 38. All have 14 digits.
+ // There are Diners Club cards that begin with 5 and have 16 digits.
+ // These are a joint venture between Diners Club and MasterCard, and should be processed like a MasterCard.
+ CardScheme::DINERS => [
+ '/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/D',
+ ],
+ // Discover card numbers begin with 6011, 622126 through 622925, 644 through 649 or 65.
+ // All have 16 digits.
+ CardScheme::DISCOVER => [
+ '/^6011[0-9]{12}$/D',
+ '/^64[4-9][0-9]{13}$/D',
+ '/^65[0-9]{14}$/D',
+ '/^622(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|91[0-9]|92[0-5])[0-9]{10}$/D',
+ ],
+ // InstaPayment cards begin with 637 through 639 and have 16 digits.
+ CardScheme::INSTAPAYMENT => [
+ '/^63[7-9][0-9]{13}$/D',
+ ],
+ // JCB cards beginning with 2131 or 1800 have 15 digits.
+ // JCB cards beginning with 35 have 16 digits.
+ CardScheme::JCB => [
+ '/^(?:2131|1800|35[0-9]{3})[0-9]{11}$/D',
+ ],
+ // Laser cards begin with either 6304, 6706, 6709 or 6771 and have between 16 and 19 digits.
+ CardScheme::LASER => [
+ '/^(6304|670[69]|6771)[0-9]{12,15}$/D',
+ ],
+ // Maestro international cards begin with 675900..675999 and have between 12 and 19 digits.
+ // Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits.
+ CardScheme::MAESTRO => [
+ '/^(6759[0-9]{2})[0-9]{6,13}$/D',
+ '/^(50[0-9]{4})[0-9]{6,13}$/D',
+ '/^5[6-9][0-9]{10,17}$/D',
+ '/^6[0-9]{11,18}$/D',
+ ],
+ // All MasterCard numbers start with the numbers 51 through 55. All have 16 digits.
+ // October 2016 MasterCard numbers can also start with 222100 through 272099.
+ CardScheme::MASTERCARD => [
+ '/^5[1-5][0-9]{14}$/D',
+ '/^2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12})$/D',
+ ],
+ // Payment system MIR numbers start with 220, then 1 digit from 0 to 4, then between 12 and 15 digits
+ CardScheme::MIR => [
+ '/^220[0-4][0-9]{12,15}$/D',
+ ],
+ // All UATP card numbers start with a 1 and have a length of 15 digits.
+ CardScheme::UATP => [
+ '/^1[0-9]{14}$/D',
+ ],
+ // All Visa card numbers start with a 4 and have a length of 13, 16, or 19 digits.
+ CardScheme::VISA => [
+ '/^4([0-9]{12}|[0-9]{15}|[0-9]{18})$/D',
+ ],
+ ];
+
+ /**
+ * Validates a creditcard belongs to a specified scheme.
+ *
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof CardScheme) {
+ throw new UnexpectedTypeException($constraint, CardScheme::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!is_numeric($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(CardScheme::NOT_NUMERIC_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $schemes = array_flip((array) $constraint->schemes);
+ $schemeRegexes = array_intersect_key($this->schemes, $schemes);
+
+ foreach ($schemeRegexes as $regexes) {
+ foreach ($regexes as $regex) {
+ if (preg_match($regex, $value)) {
+ return;
+ }
+ }
+ }
+
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(CardScheme::INVALID_FORMAT_ERROR)
+ ->addViolation();
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Cascade.php b/lib/symfony/validator/Constraints/Cascade.php
new file mode 100644
index 0000000000..5a0fce110a
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Cascade.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @Annotation
+ * @Target({"CLASS"})
+ *
+ * @author Jules Pietri
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class Cascade extends Constraint
+{
+ public array $exclude = [];
+
+ public function __construct(array|string|null $exclude = null, ?array $options = null)
+ {
+ if (\is_array($exclude) && !array_is_list($exclude)) {
+ $options = array_merge($exclude, $options ?? []);
+ $options['exclude'] = array_flip((array) ($options['exclude'] ?? []));
+ } else {
+ $this->exclude = array_flip((array) $exclude);
+ }
+
+ if (\is_array($options) && \array_key_exists('groups', $options)) {
+ throw new ConstraintDefinitionException(\sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
+ }
+
+ parent::__construct($options);
+ }
+
+ public function getTargets(): string|array
+ {
+ return self::CLASS_CONSTRAINT;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Choice.php b/lib/symfony/validator/Constraints/Choice.php
new file mode 100644
index 0000000000..7345e26403
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Choice.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Choice extends Constraint
+{
+ public const NO_SUCH_CHOICE_ERROR = '8e179f1b-97aa-4560-a02f-2a8b42e49df7';
+ public const TOO_FEW_ERROR = '11edd7eb-5872-4b6e-9f12-89923999fd0e';
+ public const TOO_MANY_ERROR = '9bd98e49-211c-433f-8630-fd1c2d0f08c3';
+
+ protected const ERROR_NAMES = [
+ self::NO_SUCH_CHOICE_ERROR => 'NO_SUCH_CHOICE_ERROR',
+ self::TOO_FEW_ERROR => 'TOO_FEW_ERROR',
+ self::TOO_MANY_ERROR => 'TOO_MANY_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $choices;
+ /** @var callable|string|null */
+ public $callback;
+ public $multiple = false;
+ public $strict = true;
+ public $min;
+ public $max;
+ public $message = 'The value you selected is not a valid choice.';
+ public $multipleMessage = 'One or more of the given values is invalid.';
+ public $minMessage = 'You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.';
+ public $maxMessage = 'You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.';
+ public bool $match = true;
+
+ public function getDefaultOption(): ?string
+ {
+ return 'choices';
+ }
+
+ public function __construct(
+ string|array $options = [],
+ ?array $choices = null,
+ callable|string|null $callback = null,
+ ?bool $multiple = null,
+ ?bool $strict = null,
+ ?int $min = null,
+ ?int $max = null,
+ ?string $message = null,
+ ?string $multipleMessage = null,
+ ?string $minMessage = null,
+ ?string $maxMessage = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ?bool $match = null,
+ ) {
+ if (\is_array($options) && $options && array_is_list($options)) {
+ $choices ??= $options;
+ $options = [];
+ }
+ if (null !== $choices) {
+ $options['value'] = $choices;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->callback = $callback ?? $this->callback;
+ $this->multiple = $multiple ?? $this->multiple;
+ $this->strict = $strict ?? $this->strict;
+ $this->min = $min ?? $this->min;
+ $this->max = $max ?? $this->max;
+ $this->message = $message ?? $this->message;
+ $this->multipleMessage = $multipleMessage ?? $this->multipleMessage;
+ $this->minMessage = $minMessage ?? $this->minMessage;
+ $this->maxMessage = $maxMessage ?? $this->maxMessage;
+ $this->match = $match ?? $this->match;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ChoiceValidator.php b/lib/symfony/validator/Constraints/ChoiceValidator.php
new file mode 100644
index 0000000000..d5c2b8c2b8
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ChoiceValidator.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\RuntimeException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * ChoiceValidator validates that the value is one of the expected values.
+ *
+ * @author Fabien Potencier
+ * @author Florian Eckerstorfer
+ * @author Bernhard Schussek
+ */
+class ChoiceValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Choice) {
+ throw new UnexpectedTypeException($constraint, Choice::class);
+ }
+
+ if (!\is_array($constraint->choices) && !$constraint->callback) {
+ throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice.');
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ if ($constraint->multiple && !\is_array($value)) {
+ throw new UnexpectedValueException($value, 'array');
+ }
+
+ if ($constraint->callback) {
+ if (!\is_callable($choices = [$this->context->getObject(), $constraint->callback])
+ && !\is_callable($choices = [$this->context->getClassName(), $constraint->callback])
+ && !\is_callable($choices = $constraint->callback)
+ ) {
+ throw new ConstraintDefinitionException('The Choice constraint expects a valid callback.');
+ }
+ $choices = $choices();
+ if (!\is_array($choices)) {
+ throw new ConstraintDefinitionException(\sprintf('The Choice constraint callback "%s" is expected to return an array, but returned "%s".', trim($this->formatValue($constraint->callback), '"'), get_debug_type($choices)));
+ }
+ } else {
+ $choices = $constraint->choices;
+ }
+
+ if (true !== $constraint->strict) {
+ throw new RuntimeException('The "strict" option of the Choice constraint should not be used.');
+ }
+
+ if ($constraint->multiple) {
+ foreach ($value as $_value) {
+ if ($constraint->match xor \in_array($_value, $choices, true)) {
+ $this->context->buildViolation($constraint->multipleMessage)
+ ->setParameter('{{ value }}', $this->formatValue($_value))
+ ->setParameter('{{ choices }}', $this->formatValues($choices))
+ ->setCode(Choice::NO_SUCH_CHOICE_ERROR)
+ ->setInvalidValue($_value)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ $count = \count($value);
+
+ if (null !== $constraint->min && $count < $constraint->min) {
+ $this->context->buildViolation($constraint->minMessage)
+ ->setParameter('{{ limit }}', $constraint->min)
+ ->setPlural((int) $constraint->min)
+ ->setCode(Choice::TOO_FEW_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (null !== $constraint->max && $count > $constraint->max) {
+ $this->context->buildViolation($constraint->maxMessage)
+ ->setParameter('{{ limit }}', $constraint->max)
+ ->setPlural((int) $constraint->max)
+ ->setCode(Choice::TOO_MANY_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ } elseif ($constraint->match xor \in_array($value, $choices, true)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setParameter('{{ choices }}', $this->formatValues($choices))
+ ->setCode(Choice::NO_SUCH_CHOICE_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Cidr.php b/lib/symfony/validator/Constraints/Cidr.php
new file mode 100644
index 0000000000..92563d84d4
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Cidr.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * Validates that a value is a valid CIDR notation.
+ *
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Sorin Pop
+ * @author Calin Bolea
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Cidr extends Constraint
+{
+ public const INVALID_CIDR_ERROR = '5649e53a-5afb-47c5-a360-ffbab3be8567';
+ public const OUT_OF_RANGE_ERROR = 'b9f14a51-acbd-401a-a078-8c6b204ab32f';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_CIDR_ERROR => 'INVALID_CIDR_ERROR',
+ self::OUT_OF_RANGE_ERROR => 'OUT_OF_RANGE_VIOLATION',
+ ];
+
+ private const NET_MAXES = [
+ Ip::ALL => 128,
+ Ip::V4 => 32,
+ Ip::V6 => 128,
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $version = Ip::ALL;
+
+ public $message = 'This value is not a valid CIDR notation.';
+
+ public $netmaskRangeViolationMessage = 'The value of the netmask should be between {{ min }} and {{ max }}.';
+
+ public $netmaskMin = 0;
+
+ public $netmaskMax;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $version = null,
+ ?int $netmaskMin = null,
+ ?int $netmaskMax = null,
+ ?string $message = null,
+ ?array $groups = null,
+ $payload = null,
+ ) {
+ $this->version = $version ?? $options['version'] ?? $this->version;
+
+ if (!\array_key_exists($this->version, self::NET_MAXES)) {
+ throw new ConstraintDefinitionException(\sprintf('The option "version" must be one of "%s".', implode('", "', array_keys(self::NET_MAXES))));
+ }
+
+ $this->netmaskMin = $netmaskMin ?? $options['netmaskMin'] ?? $this->netmaskMin;
+ $this->netmaskMax = $netmaskMax ?? $options['netmaskMax'] ?? self::NET_MAXES[$this->version];
+ $this->message = $message ?? $this->message;
+
+ unset($options['netmaskMin'], $options['netmaskMax'], $options['version']);
+
+ if ($this->netmaskMin < 0 || $this->netmaskMax > self::NET_MAXES[$this->version] || $this->netmaskMin > $this->netmaskMax) {
+ throw new ConstraintDefinitionException(\sprintf('The netmask range must be between 0 and %d.', self::NET_MAXES[$this->version]));
+ }
+
+ parent::__construct($options, $groups, $payload);
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CidrValidator.php b/lib/symfony/validator/Constraints/CidrValidator.php
new file mode 100644
index 0000000000..c90ebcfae3
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CidrValidator.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+class CidrValidator extends ConstraintValidator
+{
+ public function validate($value, Constraint $constraint): void
+ {
+ if (!$constraint instanceof Cidr) {
+ throw new UnexpectedTypeException($constraint, Cidr::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_string($value)) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $cidrParts = explode('/', $value, 2);
+
+ if (!isset($cidrParts[1])
+ || !ctype_digit($cidrParts[1])
+ || '' === $cidrParts[0]
+ ) {
+ $this->context
+ ->buildViolation($constraint->message)
+ ->setCode(Cidr::INVALID_CIDR_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $ipAddress = $cidrParts[0];
+ $netmask = (int) $cidrParts[1];
+
+ $validV4 = Ip::V6 !== $constraint->version
+ && filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)
+ && $netmask <= 32;
+
+ $validV6 = Ip::V4 !== $constraint->version
+ && filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
+
+ if (!$validV4 && !$validV6) {
+ $this->context
+ ->buildViolation($constraint->message)
+ ->setCode(Cidr::INVALID_CIDR_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if ($netmask < $constraint->netmaskMin || $netmask > $constraint->netmaskMax) {
+ $this->context
+ ->buildViolation($constraint->netmaskRangeViolationMessage)
+ ->setParameter('{{ min }}', $constraint->netmaskMin)
+ ->setParameter('{{ max }}', $constraint->netmaskMax)
+ ->setCode(Cidr::OUT_OF_RANGE_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Collection.php b/lib/symfony/validator/Constraints/Collection.php
new file mode 100644
index 0000000000..edad4fa0b1
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Collection.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Collection extends Composite
+{
+ public const MISSING_FIELD_ERROR = '2fa2158c-2a7f-484b-98aa-975522539ff8';
+ public const NO_SUCH_FIELD_ERROR = '7703c766-b5d5-4cef-ace7-ae0dd82304e9';
+
+ protected const ERROR_NAMES = [
+ self::MISSING_FIELD_ERROR => 'MISSING_FIELD_ERROR',
+ self::NO_SUCH_FIELD_ERROR => 'NO_SUCH_FIELD_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $fields = [];
+ public $allowExtraFields = false;
+ public $allowMissingFields = false;
+ public $extraFieldsMessage = 'This field was not expected.';
+ public $missingFieldsMessage = 'This field is missing.';
+
+ public function __construct(mixed $fields = null, ?array $groups = null, mixed $payload = null, ?bool $allowExtraFields = null, ?bool $allowMissingFields = null, ?string $extraFieldsMessage = null, ?string $missingFieldsMessage = null)
+ {
+ if (self::isFieldsOption($fields)) {
+ $fields = ['fields' => $fields];
+ }
+
+ parent::__construct($fields, $groups, $payload);
+
+ $this->allowExtraFields = $allowExtraFields ?? $this->allowExtraFields;
+ $this->allowMissingFields = $allowMissingFields ?? $this->allowMissingFields;
+ $this->extraFieldsMessage = $extraFieldsMessage ?? $this->extraFieldsMessage;
+ $this->missingFieldsMessage = $missingFieldsMessage ?? $this->missingFieldsMessage;
+ }
+
+ /**
+ * @return void
+ */
+ protected function initializeNestedConstraints()
+ {
+ parent::initializeNestedConstraints();
+
+ if (!\is_array($this->fields)) {
+ throw new ConstraintDefinitionException(\sprintf('The option "fields" is expected to be an array in constraint "%s".', __CLASS__));
+ }
+
+ foreach ($this->fields as $fieldName => $field) {
+ // the XmlFileLoader and YamlFileLoader pass the field Optional
+ // and Required constraint as an array with exactly one element
+ if (\is_array($field) && 1 == \count($field)) {
+ $this->fields[$fieldName] = $field = $field[0];
+ }
+
+ if (!$field instanceof Optional && !$field instanceof Required) {
+ $this->fields[$fieldName] = new Required($field);
+ }
+ }
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['fields'];
+ }
+
+ protected function getCompositeOption(): string
+ {
+ return 'fields';
+ }
+
+ private static function isFieldsOption($options): bool
+ {
+ if (!\is_array($options)) {
+ return false;
+ }
+
+ foreach ($options as $optionOrField) {
+ if ($optionOrField instanceof Constraint) {
+ return true;
+ }
+
+ if (null === $optionOrField) {
+ continue;
+ }
+
+ if (!\is_array($optionOrField)) {
+ return false;
+ }
+
+ if ($optionOrField && !($optionOrField[0] ?? null) instanceof Constraint) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CollectionValidator.php b/lib/symfony/validator/Constraints/CollectionValidator.php
new file mode 100644
index 0000000000..141b50fb32
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CollectionValidator.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class CollectionValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Collection) {
+ throw new UnexpectedTypeException($constraint, Collection::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ if (!\is_array($value) && !($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
+ throw new UnexpectedValueException($value, 'array|(Traversable&ArrayAccess)');
+ }
+
+ // We need to keep the initialized context when CollectionValidator
+ // calls itself recursively (Collection constraints can be nested).
+ // Since the context of the validator is overwritten when initialize()
+ // is called for the nested constraint, the outer validator is
+ // acting on the wrong context when the nested validation terminates.
+ //
+ // A better solution - which should be approached in Symfony 3.0 - is to
+ // remove the initialize() method and pass the context as last argument
+ // to validate() instead.
+ $context = $this->context;
+
+ foreach ($constraint->fields as $field => $fieldConstraint) {
+ $existsInArray = \is_array($value) && \array_key_exists($field, $value);
+ $existsInArrayAccess = $value instanceof \ArrayAccess && $value->offsetExists($field);
+
+ if ($existsInArray || $existsInArrayAccess) {
+ if (\count($fieldConstraint->constraints) > 0) {
+ $context->getValidator()
+ ->inContext($context)
+ ->atPath('['.$field.']')
+ ->validate($value[$field], $fieldConstraint->constraints);
+ }
+ } elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) {
+ $context->buildViolation($constraint->missingFieldsMessage)
+ ->atPath('['.$field.']')
+ ->setParameter('{{ field }}', $this->formatValue($field))
+ ->setInvalidValue(null)
+ ->setCode(Collection::MISSING_FIELD_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if (!$constraint->allowExtraFields) {
+ foreach ($value as $field => $fieldValue) {
+ if (!isset($constraint->fields[$field])) {
+ $context->buildViolation($constraint->extraFieldsMessage)
+ ->atPath('['.$field.']')
+ ->setParameter('{{ field }}', $this->formatValue($field))
+ ->setInvalidValue($fieldValue)
+ ->setCode(Collection::NO_SUCH_FIELD_ERROR)
+ ->addViolation();
+ }
+ }
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Composite.php b/lib/symfony/validator/Constraints/Composite.php
new file mode 100644
index 0000000000..824c3a1743
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Composite.php
@@ -0,0 +1,157 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * A constraint that is composed of other constraints.
+ *
+ * You should never use the nested constraint instances anywhere else, because
+ * their groups are adapted when passed to the constructor of this class.
+ *
+ * If you want to create your own composite constraint, extend this class and
+ * let {@link getCompositeOption()} return the name of the property which
+ * contains the nested constraints.
+ *
+ * @author Bernhard Schussek
+ */
+abstract class Composite extends Constraint
+{
+ /**
+ * The groups of the composite and its nested constraints are made
+ * consistent using the following strategy:
+ *
+ * - If groups are passed explicitly to the composite constraint, but
+ * not to the nested constraints, the options of the composite
+ * constraint are copied to the nested constraints;
+ *
+ * - If groups are passed explicitly to the nested constraints, but not
+ * to the composite constraint, the groups of all nested constraints
+ * are merged and used as groups for the composite constraint;
+ *
+ * - If groups are passed explicitly to both the composite and its nested
+ * constraints, the groups of the nested constraints must be a subset
+ * of the groups of the composite constraint. If not, a
+ * {@link ConstraintDefinitionException} is thrown.
+ *
+ * All this is done in the constructor, because constraints can then be
+ * cached. When constraints are loaded from the cache, no more group
+ * checks need to be done.
+ */
+ public function __construct(mixed $options = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options, $groups, $payload);
+
+ $this->initializeNestedConstraints();
+
+ /** @var Constraint[] $nestedConstraints */
+ $compositeOption = $this->getCompositeOption();
+ $nestedConstraints = $this->$compositeOption;
+
+ if (!\is_array($nestedConstraints)) {
+ $nestedConstraints = [$nestedConstraints];
+ }
+
+ foreach ($nestedConstraints as $constraint) {
+ if (!$constraint instanceof Constraint) {
+ if (\is_object($constraint)) {
+ $constraint = $constraint::class;
+ }
+
+ throw new ConstraintDefinitionException(\sprintf('The value "%s" is not an instance of Constraint in constraint "%s".', $constraint, static::class));
+ }
+
+ if ($constraint instanceof Valid) {
+ throw new ConstraintDefinitionException(\sprintf('The constraint Valid cannot be nested inside constraint "%s". You can only declare the Valid constraint directly on a field or method.', static::class));
+ }
+ }
+
+ if (!isset(((array) $this)['groups'])) {
+ $mergedGroups = [];
+
+ foreach ($nestedConstraints as $constraint) {
+ foreach ($constraint->groups as $group) {
+ $mergedGroups[$group] = true;
+ }
+ }
+
+ // prevent empty composite constraint to have empty groups
+ $this->groups = array_keys($mergedGroups) ?: [self::DEFAULT_GROUP];
+ $this->$compositeOption = $nestedConstraints;
+
+ return;
+ }
+
+ foreach ($nestedConstraints as $constraint) {
+ if (isset(((array) $constraint)['groups'])) {
+ $excessGroups = array_diff($constraint->groups, $this->groups);
+
+ if (\count($excessGroups) > 0) {
+ throw new ConstraintDefinitionException(\sprintf('The group(s) "%s" passed to the constraint "%s" should also be passed to its containing constraint "%s".', implode('", "', $excessGroups), get_debug_type($constraint), static::class));
+ }
+ } else {
+ $constraint->groups = $this->groups;
+ }
+ }
+
+ $this->$compositeOption = $nestedConstraints;
+ }
+
+ /**
+ * Implicit group names are forwarded to nested constraints.
+ *
+ * @return void
+ */
+ public function addImplicitGroupName(string $group)
+ {
+ parent::addImplicitGroupName($group);
+
+ /** @var Constraint[] $nestedConstraints */
+ $nestedConstraints = $this->{$this->getCompositeOption()};
+
+ foreach ($nestedConstraints as $constraint) {
+ $constraint->addImplicitGroupName($group);
+ }
+ }
+
+ /**
+ * Returns the name of the property that contains the nested constraints.
+ */
+ abstract protected function getCompositeOption(): string;
+
+ /**
+ * @internal Used by metadata
+ *
+ * @return Constraint[]
+ */
+ public function getNestedConstraints(): array
+ {
+ /** @var Constraint[] $nestedConstraints */
+ return $this->{$this->getCompositeOption()};
+ }
+
+ /**
+ * Initializes the nested constraints.
+ *
+ * This method can be overwritten in subclasses to clean up the nested
+ * constraints passed to the constructor.
+ *
+ * @see Collection::initializeNestedConstraints()
+ *
+ * @return void
+ */
+ protected function initializeNestedConstraints()
+ {
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Compound.php b/lib/symfony/validator/Constraints/Compound.php
new file mode 100644
index 0000000000..aa48dbe5ea
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Compound.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * Extend this class to create a reusable set of constraints.
+ *
+ * @author Maxime Steinhausser
+ */
+abstract class Compound extends Composite
+{
+ /** @var Constraint[] */
+ public $constraints = [];
+
+ public function __construct(mixed $options = null)
+ {
+ if (isset($options[$this->getCompositeOption()])) {
+ throw new ConstraintDefinitionException(\sprintf('You can\'t redefine the "%s" option. Use the "%s::getConstraints()" method instead.', $this->getCompositeOption(), __CLASS__));
+ }
+
+ $this->constraints = $this->getConstraints($this->normalizeOptions($options));
+
+ parent::__construct($options);
+ }
+
+ final protected function getCompositeOption(): string
+ {
+ return 'constraints';
+ }
+
+ final public function validatedBy(): string
+ {
+ return CompoundValidator::class;
+ }
+
+ /**
+ * @return Constraint[]
+ */
+ abstract protected function getConstraints(array $options): array;
+}
diff --git a/lib/symfony/validator/Constraints/CompoundValidator.php b/lib/symfony/validator/Constraints/CompoundValidator.php
new file mode 100644
index 0000000000..8f9c713c79
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CompoundValidator.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Maxime Steinhausser
+ */
+class CompoundValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Compound) {
+ throw new UnexpectedTypeException($constraint, Compound::class);
+ }
+
+ $context = $this->context;
+
+ $validator = $context->getValidator()->inContext($context);
+
+ $validator->validate($value, $constraint->constraints);
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Count.php b/lib/symfony/validator/Constraints/Count.php
new file mode 100644
index 0000000000..42cf6c196b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Count.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Count extends Constraint
+{
+ public const TOO_FEW_ERROR = 'bef8e338-6ae5-4caf-b8e2-50e7b0579e69';
+ public const TOO_MANY_ERROR = '756b1212-697c-468d-a9ad-50dd783bb169';
+ public const NOT_EQUAL_COUNT_ERROR = '9fe5d43f-3784-4ece-a0e1-473fc02dadbc';
+ public const NOT_DIVISIBLE_BY_ERROR = DivisibleBy::NOT_DIVISIBLE_BY;
+
+ protected const ERROR_NAMES = [
+ self::TOO_FEW_ERROR => 'TOO_FEW_ERROR',
+ self::TOO_MANY_ERROR => 'TOO_MANY_ERROR',
+ self::NOT_EQUAL_COUNT_ERROR => 'NOT_EQUAL_COUNT_ERROR',
+ self::NOT_DIVISIBLE_BY_ERROR => 'NOT_DIVISIBLE_BY_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $minMessage = 'This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.';
+ public $maxMessage = 'This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.';
+ public $exactMessage = 'This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.';
+ public $divisibleByMessage = 'The number of elements in this collection should be a multiple of {{ compared_value }}.';
+ public $min;
+ public $max;
+ public $divisibleBy;
+
+ public function __construct(
+ int|array|null $exactly = null,
+ ?int $min = null,
+ ?int $max = null,
+ ?int $divisibleBy = null,
+ ?string $exactMessage = null,
+ ?string $minMessage = null,
+ ?string $maxMessage = null,
+ ?string $divisibleByMessage = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array $options = [],
+ ) {
+ if (\is_array($exactly)) {
+ $options = array_merge($exactly, $options);
+ $exactly = $options['value'] ?? null;
+ }
+
+ $min ??= $options['min'] ?? null;
+ $max ??= $options['max'] ?? null;
+
+ unset($options['value'], $options['min'], $options['max']);
+
+ if (null !== $exactly && null === $min && null === $max) {
+ $min = $max = $exactly;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->min = $min;
+ $this->max = $max;
+ $this->divisibleBy = $divisibleBy ?? $this->divisibleBy;
+ $this->exactMessage = $exactMessage ?? $this->exactMessage;
+ $this->minMessage = $minMessage ?? $this->minMessage;
+ $this->maxMessage = $maxMessage ?? $this->maxMessage;
+ $this->divisibleByMessage = $divisibleByMessage ?? $this->divisibleByMessage;
+
+ if (null === $this->min && null === $this->max && null === $this->divisibleBy) {
+ throw new MissingOptionsException(\sprintf('Either option "min", "max" or "divisibleBy" must be given for constraint "%s".', __CLASS__), ['min', 'max', 'divisibleBy']);
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CountValidator.php b/lib/symfony/validator/Constraints/CountValidator.php
new file mode 100644
index 0000000000..3c56023538
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CountValidator.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class CountValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Count) {
+ throw new UnexpectedTypeException($constraint, Count::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ if (!\is_array($value) && !$value instanceof \Countable) {
+ throw new UnexpectedValueException($value, 'array|\Countable');
+ }
+
+ $count = \count($value);
+
+ if (null !== $constraint->max && $count > $constraint->max) {
+ $exactlyOptionEnabled = $constraint->min == $constraint->max;
+
+ $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage)
+ ->setParameter('{{ count }}', $count)
+ ->setParameter('{{ limit }}', $constraint->max)
+ ->setInvalidValue($value)
+ ->setPlural((int) $constraint->max)
+ ->setCode($exactlyOptionEnabled ? Count::NOT_EQUAL_COUNT_ERROR : Count::TOO_MANY_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (null !== $constraint->min && $count < $constraint->min) {
+ $exactlyOptionEnabled = $constraint->min == $constraint->max;
+
+ $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage)
+ ->setParameter('{{ count }}', $count)
+ ->setParameter('{{ limit }}', $constraint->min)
+ ->setInvalidValue($value)
+ ->setPlural((int) $constraint->min)
+ ->setCode($exactlyOptionEnabled ? Count::NOT_EQUAL_COUNT_ERROR : Count::TOO_FEW_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (null !== $constraint->divisibleBy) {
+ $this->context
+ ->getValidator()
+ ->inContext($this->context)
+ ->validate($count, [
+ new DivisibleBy([
+ 'value' => $constraint->divisibleBy,
+ 'message' => $constraint->divisibleByMessage,
+ ]),
+ ], $this->context->getGroup());
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Country.php b/lib/symfony/validator/Constraints/Country.php
new file mode 100644
index 0000000000..4ab5d67a89
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Country.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Countries;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Country extends Constraint
+{
+ public const NO_SUCH_COUNTRY_ERROR = '8f900c12-61bd-455d-9398-996cd040f7f0';
+
+ protected const ERROR_NAMES = [
+ self::NO_SUCH_COUNTRY_ERROR => 'NO_SUCH_COUNTRY_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid country.';
+ public $alpha3 = false;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?bool $alpha3 = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ if (!class_exists(Countries::class)) {
+ throw new LogicException('The Intl component is required to use the Country constraint. Try running "composer require symfony/intl".');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->alpha3 = $alpha3 ?? $this->alpha3;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CountryValidator.php b/lib/symfony/validator/Constraints/CountryValidator.php
new file mode 100644
index 0000000000..54c8da0f9e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CountryValidator.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Countries;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether a value is a valid country code.
+ *
+ * @author Bernhard Schussek
+ */
+class CountryValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Country) {
+ throw new UnexpectedTypeException($constraint, Country::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if ($constraint->alpha3 ? !Countries::alpha3CodeExists($value) : !Countries::exists($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Country::NO_SUCH_COUNTRY_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CssColor.php b/lib/symfony/validator/Constraints/CssColor.php
new file mode 100644
index 0000000000..e8f0aaefd1
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CssColor.php
@@ -0,0 +1,111 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Mathieu Santostefano
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class CssColor extends Constraint
+{
+ public const HEX_LONG = 'hex_long';
+ public const HEX_LONG_WITH_ALPHA = 'hex_long_with_alpha';
+ public const HEX_SHORT = 'hex_short';
+ public const HEX_SHORT_WITH_ALPHA = 'hex_short_with_alpha';
+ public const BASIC_NAMED_COLORS = 'basic_named_colors';
+ public const EXTENDED_NAMED_COLORS = 'extended_named_colors';
+ public const SYSTEM_COLORS = 'system_colors';
+ public const KEYWORDS = 'keywords';
+ public const RGB = 'rgb';
+ public const RGBA = 'rgba';
+ public const HSL = 'hsl';
+ public const HSLA = 'hsla';
+ public const INVALID_FORMAT_ERROR = '454ab47b-aacf-4059-8f26-184b2dc9d48d';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ /**
+ * @var string[]
+ */
+ private static array $validationModes = [
+ self::HEX_LONG,
+ self::HEX_LONG_WITH_ALPHA,
+ self::HEX_SHORT,
+ self::HEX_SHORT_WITH_ALPHA,
+ self::BASIC_NAMED_COLORS,
+ self::EXTENDED_NAMED_COLORS,
+ self::SYSTEM_COLORS,
+ self::KEYWORDS,
+ self::RGB,
+ self::RGBA,
+ self::HSL,
+ self::HSLA,
+ ];
+
+ public $message = 'This value is not a valid CSS color.';
+ public $formats;
+
+ /**
+ * @param array|string $formats The types of CSS colors allowed (e.g. hexadecimal only, RGB and HSL only, etc.).
+ */
+ public function __construct($formats = [], ?string $message = null, ?array $groups = null, $payload = null, ?array $options = null)
+ {
+ $validationModesAsString = implode(', ', self::$validationModes);
+
+ if (!$formats) {
+ $options['value'] = self::$validationModes;
+ } elseif (\is_array($formats) && \is_string(key($formats))) {
+ $options = array_merge($formats, $options ?? []);
+ } elseif (\is_array($formats)) {
+ if ([] === array_intersect(self::$validationModes, $formats)) {
+ throw new InvalidArgumentException(\sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString));
+ }
+
+ $options['value'] = $formats;
+ } elseif (\is_string($formats)) {
+ if (!\in_array($formats, self::$validationModes)) {
+ throw new InvalidArgumentException(\sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString));
+ }
+
+ $options['value'] = [$formats];
+ } else {
+ throw new InvalidArgumentException('The "formats" parameter type is not valid. It should be a string or an array.');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+
+ public function getDefaultOption(): string
+ {
+ return 'formats';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['formats'];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CssColorValidator.php b/lib/symfony/validator/Constraints/CssColorValidator.php
new file mode 100644
index 0000000000..78563a92c3
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CssColorValidator.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Mathieu Santostefano
+ */
+class CssColorValidator extends ConstraintValidator
+{
+ private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/iD';
+ private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/iD';
+ private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/iD';
+ private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/iD';
+ // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors
+ private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/iD';
+ // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors
+ private const PATTERN_EXTENDED_NAMED_COLORS = '/^(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/iD';
+ // List comes from https://drafts.csswg.org/css-color/#css-system-colors
+ private const PATTERN_SYSTEM_COLORS = '/^(Canvas|CanvasText|LinkText|VisitedText|ActiveText|ButtonFace|ButtonText|ButtonBorder|Field|FieldText|Highlight|HighlightText|SelectedItem|SelectedItemText|Mark|MarkText|GrayText)$/iD';
+ private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/iD';
+ private const PATTERN_RGB = '/^rgb\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s*\)$/iD';
+ private const PATTERN_RGBA = '/^rgba\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|0?\.\d+|1(\.0)?)\s*\)$/iD';
+ private const PATTERN_HSL = '/^hsl\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%\s*\)$/iD';
+ private const PATTERN_HSLA = '/^hsla\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%,\s*(0|0?\.\d+|1(\.0)?)\s*\)$/iD';
+
+ private const COLOR_PATTERNS = [
+ CssColor::HEX_LONG => self::PATTERN_HEX_LONG,
+ CssColor::HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA,
+ CssColor::HEX_SHORT => self::PATTERN_HEX_SHORT,
+ CssColor::HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA,
+ CssColor::BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS,
+ CssColor::EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS,
+ CssColor::SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS,
+ CssColor::KEYWORDS => self::PATTERN_KEYWORDS,
+ CssColor::RGB => self::PATTERN_RGB,
+ CssColor::RGBA => self::PATTERN_RGBA,
+ CssColor::HSL => self::PATTERN_HSL,
+ CssColor::HSLA => self::PATTERN_HSLA,
+ ];
+
+ public function validate($value, Constraint $constraint): void
+ {
+ if (!$constraint instanceof CssColor) {
+ throw new UnexpectedTypeException($constraint, CssColor::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_string($value)) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $formats = array_flip((array) $constraint->formats);
+ $formatRegexes = array_intersect_key(self::COLOR_PATTERNS, $formats);
+
+ foreach ($formatRegexes as $regex) {
+ if (preg_match($regex, (string) $value)) {
+ return;
+ }
+ }
+
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(CssColor::INVALID_FORMAT_ERROR)
+ ->addViolation();
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Currency.php b/lib/symfony/validator/Constraints/Currency.php
new file mode 100644
index 0000000000..facf111007
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Currency.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Currencies;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Miha Vrhovnik
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Currency extends Constraint
+{
+ public const NO_SUCH_CURRENCY_ERROR = '69945ac1-2db4-405f-bec7-d2772f73df52';
+
+ protected const ERROR_NAMES = [
+ self::NO_SUCH_CURRENCY_ERROR => 'NO_SUCH_CURRENCY_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid currency.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ if (!class_exists(Currencies::class)) {
+ throw new LogicException('The Intl component is required to use the Currency constraint. Try running "composer require symfony/intl".');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/CurrencyValidator.php b/lib/symfony/validator/Constraints/CurrencyValidator.php
new file mode 100644
index 0000000000..a50ea62ab6
--- /dev/null
+++ b/lib/symfony/validator/Constraints/CurrencyValidator.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Currencies;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether a value is a valid currency.
+ *
+ * @author Miha Vrhovnik
+ * @author Bernhard Schussek
+ */
+class CurrencyValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Currency) {
+ throw new UnexpectedTypeException($constraint, Currency::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (!Currencies::exists($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Currency::NO_SUCH_CURRENCY_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Date.php b/lib/symfony/validator/Constraints/Date.php
new file mode 100644
index 0000000000..bccb59ee6b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Date.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Date extends Constraint
+{
+ public const INVALID_FORMAT_ERROR = '69819696-02ac-4a99-9ff0-14e127c4d1bc';
+ public const INVALID_DATE_ERROR = '3c184ce5-b31d-4de7-8b76-326da7b2be93';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
+ self::INVALID_DATE_ERROR => 'INVALID_DATE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid date.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/DateTime.php b/lib/symfony/validator/Constraints/DateTime.php
new file mode 100644
index 0000000000..45648f0baf
--- /dev/null
+++ b/lib/symfony/validator/Constraints/DateTime.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class DateTime extends Constraint
+{
+ public const INVALID_FORMAT_ERROR = '1a9da513-2640-4f84-9b6a-4d99dcddc628';
+ public const INVALID_DATE_ERROR = 'd52afa47-620d-4d99-9f08-f4d85b36e33c';
+ public const INVALID_TIME_ERROR = '5e797c9d-74f7-4098-baa3-94390c447b27';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
+ self::INVALID_DATE_ERROR => 'INVALID_DATE_ERROR',
+ self::INVALID_TIME_ERROR => 'INVALID_TIME_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $format = 'Y-m-d H:i:s';
+ public $message = 'This value is not a valid datetime.';
+
+ public function __construct(string|array|null $format = null, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = [])
+ {
+ if (\is_array($format)) {
+ $options = array_merge($format, $options);
+ } elseif (null !== $format) {
+ $options['value'] = $format;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'format';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/DateTimeValidator.php b/lib/symfony/validator/Constraints/DateTimeValidator.php
new file mode 100644
index 0000000000..c88732d4d8
--- /dev/null
+++ b/lib/symfony/validator/Constraints/DateTimeValidator.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ * @author Diego Saint Esteben
+ */
+class DateTimeValidator extends DateValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof DateTime) {
+ throw new UnexpectedTypeException($constraint, DateTime::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ \DateTimeImmutable::createFromFormat($constraint->format, $value);
+
+ $errors = \DateTimeImmutable::getLastErrors() ?: ['error_count' => 0, 'warnings' => []];
+
+ if (0 < $errors['error_count']) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(DateTime::INVALID_FORMAT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (str_ends_with($constraint->format, '+')) {
+ $errors['warnings'] = array_filter($errors['warnings'], fn ($warning) => 'Trailing data' !== $warning);
+ }
+
+ foreach ($errors['warnings'] as $warning) {
+ if ('The parsed date was invalid' === $warning) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(DateTime::INVALID_DATE_ERROR)
+ ->addViolation();
+ } elseif ('The parsed time was invalid' === $warning) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(DateTime::INVALID_TIME_ERROR)
+ ->addViolation();
+ } else {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(DateTime::INVALID_FORMAT_ERROR)
+ ->addViolation();
+ }
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/DateValidator.php b/lib/symfony/validator/Constraints/DateValidator.php
new file mode 100644
index 0000000000..65dc9648ac
--- /dev/null
+++ b/lib/symfony/validator/Constraints/DateValidator.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class DateValidator extends ConstraintValidator
+{
+ public const PATTERN = '/^(?\d{4})-(?\d{2})-(?\d{2})$/D';
+
+ /**
+ * Checks whether a date is valid.
+ *
+ * @internal
+ */
+ public static function checkDate(int $year, int $month, int $day): bool
+ {
+ return checkdate($month, $day, $year);
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Date) {
+ throw new UnexpectedTypeException($constraint, Date::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (!preg_match(static::PATTERN, $value, $matches)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Date::INVALID_FORMAT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (!self::checkDate(
+ $matches['year'] ?? $matches[1],
+ $matches['month'] ?? $matches[2],
+ $matches['day'] ?? $matches[3]
+ )) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Date::INVALID_DATE_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/DisableAutoMapping.php b/lib/symfony/validator/Constraints/DisableAutoMapping.php
new file mode 100644
index 0000000000..636801ac97
--- /dev/null
+++ b/lib/symfony/validator/Constraints/DisableAutoMapping.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * Disables auto mapping.
+ *
+ * Using the annotations on a property has higher precedence than using it on a class,
+ * which has higher precedence than any configuration that might be defined outside the class.
+ *
+ * @Annotation
+ *
+ * @author Kévin Dunglas
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
+class DisableAutoMapping extends Constraint
+{
+ public function __construct(?array $options = null)
+ {
+ if (\is_array($options) && \array_key_exists('groups', $options)) {
+ throw new ConstraintDefinitionException(\sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
+ }
+
+ parent::__construct($options);
+ }
+
+ public function getTargets(): string|array
+ {
+ return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/DivisibleBy.php b/lib/symfony/validator/Constraints/DivisibleBy.php
new file mode 100644
index 0000000000..90164aab28
--- /dev/null
+++ b/lib/symfony/validator/Constraints/DivisibleBy.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Colin O'Dell
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class DivisibleBy extends AbstractComparison
+{
+ public const NOT_DIVISIBLE_BY = '6d99d6c3-1464-4ccf-bdc7-14d083cf455c';
+
+ protected const ERROR_NAMES = [
+ self::NOT_DIVISIBLE_BY => 'NOT_DIVISIBLE_BY',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be a multiple of {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/DivisibleByValidator.php b/lib/symfony/validator/Constraints/DivisibleByValidator.php
new file mode 100644
index 0000000000..d868758840
--- /dev/null
+++ b/lib/symfony/validator/Constraints/DivisibleByValidator.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates that values are a multiple of the given number.
+ *
+ * @author Colin O'Dell
+ */
+class DivisibleByValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ if (!is_numeric($value1)) {
+ throw new UnexpectedValueException($value1, 'numeric');
+ }
+
+ if (!is_numeric($value2)) {
+ throw new UnexpectedValueException($value2, 'numeric');
+ }
+
+ if (!$value2 = abs($value2)) {
+ return false;
+ }
+ if (\is_int($value1 = abs($value1)) && \is_int($value2)) {
+ return 0 === ($value1 % $value2);
+ }
+ if (!$remainder = fmod($value1, $value2)) {
+ return true;
+ }
+ if (\is_float($value2) && \INF !== $value2) {
+ $quotient = $value1 / $value2;
+ $rounded = round($quotient);
+
+ return \sprintf('%.12e', $quotient) === \sprintf('%.12e', $rounded);
+ }
+
+ return \sprintf('%.12e', $value2) === \sprintf('%.12e', $remainder);
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return DivisibleBy::NOT_DIVISIBLE_BY;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Email.php b/lib/symfony/validator/Constraints/Email.php
new file mode 100644
index 0000000000..995713c400
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Email.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Egulias\EmailValidator\EmailValidator as StrictEmailValidator;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Email extends Constraint
+{
+ public const VALIDATION_MODE_HTML5_ALLOW_NO_TLD = 'html5-allow-no-tld';
+ public const VALIDATION_MODE_HTML5 = 'html5';
+ public const VALIDATION_MODE_STRICT = 'strict';
+ /**
+ * @deprecated since Symfony 6.2, use VALIDATION_MODE_HTML5 instead
+ */
+ public const VALIDATION_MODE_LOOSE = 'loose';
+
+ public const INVALID_FORMAT_ERROR = 'bd79c0ab-ddba-46cc-a703-a7a4b08de310';
+
+ public const VALIDATION_MODES = [
+ self::VALIDATION_MODE_HTML5_ALLOW_NO_TLD,
+ self::VALIDATION_MODE_HTML5,
+ self::VALIDATION_MODE_STRICT,
+ self::VALIDATION_MODE_LOOSE,
+ ];
+
+ protected const ERROR_NAMES = [
+ self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid email address.';
+ public $mode;
+ /** @var callable|null */
+ public $normalizer;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?string $mode = null,
+ ?callable $normalizer = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ if (\is_array($options) && \array_key_exists('mode', $options) && !\in_array($options['mode'], self::VALIDATION_MODES, true)) {
+ throw new InvalidArgumentException('The "mode" parameter value is not valid.');
+ }
+
+ if (null !== $mode && !\in_array($mode, self::VALIDATION_MODES, true)) {
+ throw new InvalidArgumentException('The "mode" parameter value is not valid.');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->mode = $mode ?? $this->mode;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+
+ if (self::VALIDATION_MODE_LOOSE === $this->mode) {
+ trigger_deprecation('symfony/validator', '6.2', 'The "%s" mode is deprecated. It will be removed in 7.0 and the default mode will be changed to "%s".', self::VALIDATION_MODE_LOOSE, self::VALIDATION_MODE_HTML5);
+ }
+
+ if (self::VALIDATION_MODE_STRICT === $this->mode && !class_exists(StrictEmailValidator::class)) {
+ throw new LogicException(\sprintf('The "egulias/email-validator" component is required to use the "%s" constraint in strict mode. Try running "composer require egulias/email-validator".', __CLASS__));
+ }
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/EmailValidator.php b/lib/symfony/validator/Constraints/EmailValidator.php
new file mode 100644
index 0000000000..2883d0d555
--- /dev/null
+++ b/lib/symfony/validator/Constraints/EmailValidator.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Egulias\EmailValidator\EmailValidator as EguliasEmailValidator;
+use Egulias\EmailValidator\Validation\EmailValidation;
+use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class EmailValidator extends ConstraintValidator
+{
+ private const PATTERN_HTML5_ALLOW_NO_TLD = '/^[a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/D';
+ private const PATTERN_HTML5 = '/^[a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/D';
+ private const PATTERN_LOOSE = '/^.+\@\S+\.\S+$/D';
+
+ private const EMAIL_PATTERNS = [
+ Email::VALIDATION_MODE_LOOSE => self::PATTERN_LOOSE,
+ Email::VALIDATION_MODE_HTML5 => self::PATTERN_HTML5,
+ Email::VALIDATION_MODE_HTML5_ALLOW_NO_TLD => self::PATTERN_HTML5_ALLOW_NO_TLD,
+ ];
+
+ private string $defaultMode;
+
+ public function __construct(string $defaultMode = Email::VALIDATION_MODE_LOOSE)
+ {
+ if (!\in_array($defaultMode, Email::VALIDATION_MODES, true)) {
+ throw new InvalidArgumentException('The "defaultMode" parameter value is not valid.');
+ }
+
+ if (Email::VALIDATION_MODE_LOOSE === $defaultMode) {
+ trigger_deprecation('symfony/validator', '6.2', 'The "%s" mode is deprecated. It will be removed in 7.0 and the default mode will be changed to "%s".', Email::VALIDATION_MODE_LOOSE, Email::VALIDATION_MODE_HTML5);
+ }
+
+ $this->defaultMode = $defaultMode;
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Email) {
+ throw new UnexpectedTypeException($constraint, Email::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+ if ('' === $value) {
+ return;
+ }
+
+ if (null !== $constraint->normalizer) {
+ $value = ($constraint->normalizer)($value);
+ }
+
+ if (null === $constraint->mode) {
+ if (Email::VALIDATION_MODE_STRICT === $this->defaultMode && !class_exists(EguliasEmailValidator::class)) {
+ throw new LogicException(\sprintf('The "egulias/email-validator" component is required to make the "%s" constraint default to strict mode. Try running "composer require egulias/email-validator".', Email::class));
+ }
+
+ $constraint->mode = $this->defaultMode;
+ }
+
+ if (!\in_array($constraint->mode, Email::VALIDATION_MODES, true)) {
+ throw new InvalidArgumentException(\sprintf('The "%s::$mode" parameter value is not valid.', get_debug_type($constraint)));
+ }
+
+ if (Email::VALIDATION_MODE_STRICT === $constraint->mode) {
+ $strictValidator = new EguliasEmailValidator();
+
+ if (interface_exists(EmailValidation::class) && !$strictValidator->isValid($value, new NoRFCWarningsValidation())) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Email::INVALID_FORMAT_ERROR)
+ ->addViolation();
+
+ return;
+ } elseif (!interface_exists(EmailValidation::class) && !$strictValidator->isValid($value, false, true)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Email::INVALID_FORMAT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ } elseif (!preg_match(self::EMAIL_PATTERNS[$constraint->mode], $value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Email::INVALID_FORMAT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/EnableAutoMapping.php b/lib/symfony/validator/Constraints/EnableAutoMapping.php
new file mode 100644
index 0000000000..c0ece7547d
--- /dev/null
+++ b/lib/symfony/validator/Constraints/EnableAutoMapping.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * Enables auto mapping.
+ *
+ * Using the annotations on a property has higher precedence than using it on a class,
+ * which has higher precedence than any configuration that might be defined outside the class.
+ *
+ * @Annotation
+ *
+ * @author Kévin Dunglas
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
+class EnableAutoMapping extends Constraint
+{
+ public function __construct(?array $options = null)
+ {
+ if (\is_array($options) && \array_key_exists('groups', $options)) {
+ throw new ConstraintDefinitionException(\sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
+ }
+
+ parent::__construct($options);
+ }
+
+ public function getTargets(): string|array
+ {
+ return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/EqualTo.php b/lib/symfony/validator/Constraints/EqualTo.php
new file mode 100644
index 0000000000..03769ce8a8
--- /dev/null
+++ b/lib/symfony/validator/Constraints/EqualTo.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class EqualTo extends AbstractComparison
+{
+ public const NOT_EQUAL_ERROR = '478618a7-95ba-473d-9101-cabd45e49115';
+
+ protected const ERROR_NAMES = [
+ self::NOT_EQUAL_ERROR => 'NOT_EQUAL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be equal to {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/EqualToValidator.php b/lib/symfony/validator/Constraints/EqualToValidator.php
new file mode 100644
index 0000000000..a8b5bad9da
--- /dev/null
+++ b/lib/symfony/validator/Constraints/EqualToValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values are equal (==).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class EqualToValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return $value1 == $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return EqualTo::NOT_EQUAL_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Existence.php b/lib/symfony/validator/Constraints/Existence.php
new file mode 100644
index 0000000000..a0d6ebd606
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Existence.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @author Bernhard Schussek
+ */
+abstract class Existence extends Composite
+{
+ public $constraints = [];
+
+ public function getDefaultOption(): ?string
+ {
+ return 'constraints';
+ }
+
+ protected function getCompositeOption(): string
+ {
+ return 'constraints';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Expression.php b/lib/symfony/validator/Constraints/Expression.php
new file mode 100644
index 0000000000..e784ce9e87
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Expression.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\ExpressionLanguage\Expression as ExpressionObject;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Fabien Potencier
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
+class Expression extends Constraint
+{
+ public const EXPRESSION_FAILED_ERROR = '6b3befbc-2f01-4ddf-be21-b57898905284';
+
+ protected const ERROR_NAMES = [
+ self::EXPRESSION_FAILED_ERROR => 'EXPRESSION_FAILED_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not valid.';
+ public $expression;
+ public $values = [];
+ public bool $negate = true;
+
+ public function __construct(
+ string|ExpressionObject|array|null $expression,
+ ?string $message = null,
+ ?array $values = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array $options = [],
+ ?bool $negate = null,
+ ) {
+ if (!class_exists(ExpressionLanguage::class)) {
+ throw new LogicException(\sprintf('The "symfony/expression-language" component is required to use the "%s" constraint. Try running "composer require symfony/expression-language".', __CLASS__));
+ }
+
+ if (\is_array($expression)) {
+ $options = array_merge($expression, $options);
+ } elseif (null !== $expression) {
+ $options['value'] = $expression;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->values = $values ?? $this->values;
+ $this->negate = $negate ?? $this->negate;
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'expression';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['expression'];
+ }
+
+ public function getTargets(): string|array
+ {
+ return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
+ }
+
+ public function validatedBy(): string
+ {
+ return 'validator.expression';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ExpressionLanguageProvider.php b/lib/symfony/validator/Constraints/ExpressionLanguageProvider.php
new file mode 100644
index 0000000000..0a3727503e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ExpressionLanguageProvider.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\ExpressionLanguage\ExpressionFunction;
+use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+
+class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
+{
+ public function getFunctions(): array
+ {
+ return [
+ new ExpressionFunction('is_valid', function (...$arguments) {
+ return \sprintf(
+ '0 === $context->getValidator()->inContext($context)->validate(%s)->getViolations()->count()',
+ implode(', ', $arguments)
+ );
+ }, function (array $variables, ...$arguments): bool {
+ return 0 === $variables['context']->getValidator()->inContext($variables['context'])->validate(...$arguments)->getViolations()->count();
+ }),
+ ];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ExpressionLanguageSyntax.php b/lib/symfony/validator/Constraints/ExpressionLanguageSyntax.php
new file mode 100644
index 0000000000..4f9bbe256d
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ExpressionLanguageSyntax.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+trigger_deprecation('symfony/validator', '6.1', 'The "%s" constraint is deprecated since symfony 6.1, use "ExpressionSyntax" instead.', ExpressionLanguageSyntax::class);
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Andrey Sevastianov
+ *
+ * @deprecated since symfony 6.1, use ExpressionSyntax instead
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ExpressionLanguageSyntax extends Constraint
+{
+ public const EXPRESSION_LANGUAGE_SYNTAX_ERROR = '1766a3f3-ff03-40eb-b053-ab7aa23d988a';
+
+ protected const ERROR_NAMES = [
+ self::EXPRESSION_LANGUAGE_SYNTAX_ERROR => 'EXPRESSION_LANGUAGE_SYNTAX_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be a valid expression.';
+ public $service;
+ public $allowedVariables;
+
+ public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $allowedVariables = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->service = $service ?? $this->service;
+ $this->allowedVariables = $allowedVariables ?? $this->allowedVariables;
+ }
+
+ public function validatedBy(): string
+ {
+ return $this->service ?? static::class.'Validator';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php b/lib/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php
new file mode 100644
index 0000000000..3290e1c05e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\ExpressionLanguage\SyntaxError;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+trigger_deprecation('symfony/validator', '6.1', 'The "%s" constraint is deprecated since symfony 6.1, use "ExpressionSyntaxValidator" instead.', ExpressionLanguageSyntaxValidator::class);
+
+/**
+ * @author Andrey Sevastianov
+ *
+ * @deprecated since symfony 6.1, use ExpressionSyntaxValidator instead
+ */
+class ExpressionLanguageSyntaxValidator extends ConstraintValidator
+{
+ private ?ExpressionLanguage $expressionLanguage;
+
+ public function __construct(?ExpressionLanguage $expressionLanguage = null)
+ {
+ if (!class_exists(ExpressionLanguage::class)) {
+ throw new \LogicException(\sprintf('The "%s" class requires the "ExpressionLanguage" component. Try running "composer require symfony/expression-language".', self::class));
+ }
+
+ $this->expressionLanguage = $expressionLanguage;
+ }
+
+ public function validate(mixed $expression, Constraint $constraint): void
+ {
+ if (!$constraint instanceof ExpressionLanguageSyntax) {
+ throw new UnexpectedTypeException($constraint, ExpressionLanguageSyntax::class);
+ }
+
+ if (!\is_string($expression)) {
+ throw new UnexpectedValueException($expression, 'string');
+ }
+
+ $this->expressionLanguage ??= new ExpressionLanguage();
+
+ try {
+ $this->expressionLanguage->lint($expression, $constraint->allowedVariables);
+ } catch (SyntaxError $exception) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ syntax_error }}', $this->formatValue($exception->getMessage()))
+ ->setInvalidValue((string) $expression)
+ ->setCode(ExpressionLanguageSyntax::EXPRESSION_LANGUAGE_SYNTAX_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ExpressionSyntax.php b/lib/symfony/validator/Constraints/ExpressionSyntax.php
new file mode 100644
index 0000000000..2d2eb18a1f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ExpressionSyntax.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Andrey Sevastianov
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ExpressionSyntax extends Constraint
+{
+ public const EXPRESSION_SYNTAX_ERROR = 'e219aa22-8b11-48ec-81a0-fc07cdb0e13f';
+
+ protected const ERROR_NAMES = [
+ self::EXPRESSION_SYNTAX_ERROR => 'EXPRESSION_SYNTAX_ERROR',
+ ];
+
+ public $message = 'This value should be a valid expression.';
+ public $service;
+ public $allowedVariables;
+
+ public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $allowedVariables = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->service = $service ?? $this->service;
+ $this->allowedVariables = $allowedVariables ?? $this->allowedVariables;
+ }
+
+ public function validatedBy(): string
+ {
+ return $this->service ?? static::class.'Validator';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ExpressionSyntaxValidator.php b/lib/symfony/validator/Constraints/ExpressionSyntaxValidator.php
new file mode 100644
index 0000000000..36db35c63b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ExpressionSyntaxValidator.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\ExpressionLanguage\SyntaxError;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Andrey Sevastianov
+ */
+class ExpressionSyntaxValidator extends ConstraintValidator
+{
+ private ?ExpressionLanguage $expressionLanguage;
+
+ public function __construct(?ExpressionLanguage $expressionLanguage = null)
+ {
+ $this->expressionLanguage = $expressionLanguage;
+ }
+
+ public function validate(mixed $expression, Constraint $constraint): void
+ {
+ if (!$constraint instanceof ExpressionSyntax) {
+ throw new UnexpectedTypeException($constraint, ExpressionSyntax::class);
+ }
+
+ if (null === $expression || '' === $expression) {
+ return;
+ }
+
+ if (!\is_string($expression)) {
+ throw new UnexpectedValueException($expression, 'string');
+ }
+
+ $this->expressionLanguage ??= new ExpressionLanguage();
+
+ try {
+ $this->expressionLanguage->lint($expression, $constraint->allowedVariables);
+ } catch (SyntaxError $exception) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ syntax_error }}', $this->formatValue($exception->getMessage()))
+ ->setInvalidValue((string) $expression)
+ ->setCode(ExpressionSyntax::EXPRESSION_SYNTAX_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ExpressionValidator.php b/lib/symfony/validator/Constraints/ExpressionValidator.php
new file mode 100644
index 0000000000..9eddbc4397
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ExpressionValidator.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Fabien Potencier
+ * @author Bernhard Schussek
+ */
+class ExpressionValidator extends ConstraintValidator
+{
+ private ExpressionLanguage $expressionLanguage;
+
+ public function __construct(?ExpressionLanguage $expressionLanguage = null)
+ {
+ if ($expressionLanguage) {
+ $this->expressionLanguage = $expressionLanguage;
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Expression) {
+ throw new UnexpectedTypeException($constraint, Expression::class);
+ }
+
+ $variables = $constraint->values;
+ $variables['value'] = $value;
+ $variables['this'] = $this->context->getObject();
+ $variables['context'] = $this->context;
+
+ if ($constraint->negate xor $this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING))
+ ->setCode(Expression::EXPRESSION_FAILED_ERROR)
+ ->addViolation();
+ }
+ }
+
+ private function getExpressionLanguage(): ExpressionLanguage
+ {
+ if (!isset($this->expressionLanguage)) {
+ $this->expressionLanguage = new ExpressionLanguage();
+ $this->expressionLanguage->registerProvider(new ExpressionLanguageProvider());
+ }
+
+ return $this->expressionLanguage;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/File.php b/lib/symfony/validator/Constraints/File.php
new file mode 100644
index 0000000000..88712a7a2d
--- /dev/null
+++ b/lib/symfony/validator/Constraints/File.php
@@ -0,0 +1,186 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @property int $maxSize
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class File extends Constraint
+{
+ // Check the Image constraint for clashes if adding new constants here
+
+ public const NOT_FOUND_ERROR = 'd2a3fb6e-7ddc-4210-8fbf-2ab345ce1998';
+ public const NOT_READABLE_ERROR = 'c20c92a4-5bfa-4202-9477-28e800e0f6ff';
+ public const EMPTY_ERROR = '5d743385-9775-4aa5-8ff5-495fb1e60137';
+ public const TOO_LARGE_ERROR = 'df8637af-d466-48c6-a59d-e7126250a654';
+ public const INVALID_MIME_TYPE_ERROR = '744f00bc-4389-4c74-92de-9a43cde55534';
+ public const INVALID_EXTENSION_ERROR = 'c8c7315c-6186-4719-8b71-5659e16bdcb7';
+ public const FILENAME_TOO_LONG = 'e5706483-91a8-49d8-9a59-5e81a3c634a8';
+
+ protected const ERROR_NAMES = [
+ self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR',
+ self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR',
+ self::EMPTY_ERROR => 'EMPTY_ERROR',
+ self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
+ self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR',
+ self::INVALID_EXTENSION_ERROR => 'INVALID_EXTENSION_ERROR',
+ self::FILENAME_TOO_LONG => 'FILENAME_TOO_LONG',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $binaryFormat;
+ public $mimeTypes = [];
+ public ?int $filenameMaxLength = null;
+ public array|string|null $extensions = [];
+ public $notFoundMessage = 'The file could not be found.';
+ public $notReadableMessage = 'The file is not readable.';
+ public $maxSizeMessage = 'The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.';
+ public $mimeTypesMessage = 'The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.';
+ public string $extensionsMessage = 'The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}.';
+ public $disallowEmptyMessage = 'An empty file is not allowed.';
+ public $filenameTooLongMessage = 'The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less.';
+
+ public $uploadIniSizeErrorMessage = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.';
+ public $uploadFormSizeErrorMessage = 'The file is too large.';
+ public $uploadPartialErrorMessage = 'The file was only partially uploaded.';
+ public $uploadNoFileErrorMessage = 'No file was uploaded.';
+ public $uploadNoTmpDirErrorMessage = 'No temporary folder was configured in php.ini.';
+ public $uploadCantWriteErrorMessage = 'Cannot write temporary file to disk.';
+ public $uploadExtensionErrorMessage = 'A PHP extension caused the upload to fail.';
+ public $uploadErrorMessage = 'The file could not be uploaded.';
+
+ protected $maxSize;
+
+ /**
+ * @param array|string $extensions
+ */
+ public function __construct(
+ ?array $options = null,
+ int|string|null $maxSize = null,
+ ?bool $binaryFormat = null,
+ array|string|null $mimeTypes = null,
+ ?int $filenameMaxLength = null,
+ ?string $notFoundMessage = null,
+ ?string $notReadableMessage = null,
+ ?string $maxSizeMessage = null,
+ ?string $mimeTypesMessage = null,
+ ?string $disallowEmptyMessage = null,
+ ?string $filenameTooLongMessage = null,
+
+ ?string $uploadIniSizeErrorMessage = null,
+ ?string $uploadFormSizeErrorMessage = null,
+ ?string $uploadPartialErrorMessage = null,
+ ?string $uploadNoFileErrorMessage = null,
+ ?string $uploadNoTmpDirErrorMessage = null,
+ ?string $uploadCantWriteErrorMessage = null,
+ ?string $uploadExtensionErrorMessage = null,
+ ?string $uploadErrorMessage = null,
+ ?array $groups = null,
+ mixed $payload = null,
+
+ array|string|null $extensions = null,
+ ?string $extensionsMessage = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->maxSize = $maxSize ?? $this->maxSize;
+ $this->binaryFormat = $binaryFormat ?? $this->binaryFormat;
+ $this->mimeTypes = $mimeTypes ?? $this->mimeTypes;
+ $this->filenameMaxLength = $filenameMaxLength ?? $this->filenameMaxLength;
+ $this->extensions = $extensions ?? $this->extensions;
+ $this->notFoundMessage = $notFoundMessage ?? $this->notFoundMessage;
+ $this->notReadableMessage = $notReadableMessage ?? $this->notReadableMessage;
+ $this->maxSizeMessage = $maxSizeMessage ?? $this->maxSizeMessage;
+ $this->mimeTypesMessage = $mimeTypesMessage ?? $this->mimeTypesMessage;
+ $this->extensionsMessage = $extensionsMessage ?? $this->extensionsMessage;
+ $this->disallowEmptyMessage = $disallowEmptyMessage ?? $this->disallowEmptyMessage;
+ $this->filenameTooLongMessage = $filenameTooLongMessage ?? $this->filenameTooLongMessage;
+ $this->uploadIniSizeErrorMessage = $uploadIniSizeErrorMessage ?? $this->uploadIniSizeErrorMessage;
+ $this->uploadFormSizeErrorMessage = $uploadFormSizeErrorMessage ?? $this->uploadFormSizeErrorMessage;
+ $this->uploadPartialErrorMessage = $uploadPartialErrorMessage ?? $this->uploadPartialErrorMessage;
+ $this->uploadNoFileErrorMessage = $uploadNoFileErrorMessage ?? $this->uploadNoFileErrorMessage;
+ $this->uploadNoTmpDirErrorMessage = $uploadNoTmpDirErrorMessage ?? $this->uploadNoTmpDirErrorMessage;
+ $this->uploadCantWriteErrorMessage = $uploadCantWriteErrorMessage ?? $this->uploadCantWriteErrorMessage;
+ $this->uploadExtensionErrorMessage = $uploadExtensionErrorMessage ?? $this->uploadExtensionErrorMessage;
+ $this->uploadErrorMessage = $uploadErrorMessage ?? $this->uploadErrorMessage;
+
+ if (null !== $this->maxSize) {
+ $this->normalizeBinaryFormat($this->maxSize);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function __set(string $option, mixed $value)
+ {
+ if ('maxSize' === $option) {
+ $this->normalizeBinaryFormat($value);
+
+ return;
+ }
+
+ parent::__set($option, $value);
+ }
+
+ public function __get(string $option): mixed
+ {
+ if ('maxSize' === $option) {
+ return $this->maxSize;
+ }
+
+ return parent::__get($option);
+ }
+
+ public function __isset(string $option): bool
+ {
+ if ('maxSize' === $option) {
+ return true;
+ }
+
+ return parent::__isset($option);
+ }
+
+ private function normalizeBinaryFormat(int|string $maxSize): void
+ {
+ $factors = [
+ 'k' => 1000,
+ 'ki' => 1 << 10,
+ 'm' => 1000 * 1000,
+ 'mi' => 1 << 20,
+ 'g' => 1000 * 1000 * 1000,
+ 'gi' => 1 << 30,
+ ];
+ if (ctype_digit((string) $maxSize)) {
+ $this->maxSize = (int) $maxSize;
+ $this->binaryFormat ??= false;
+ } elseif (preg_match('/^(\d++)('.implode('|', array_keys($factors)).')$/i', $maxSize, $matches)) {
+ $this->maxSize = $matches[1] * $factors[$unit = strtolower($matches[2])];
+ $this->binaryFormat ??= 2 === \strlen($unit);
+ } else {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum size.', $maxSize));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/FileValidator.php b/lib/symfony/validator/Constraints/FileValidator.php
new file mode 100644
index 0000000000..6346ad098f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/FileValidator.php
@@ -0,0 +1,309 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\HttpFoundation\File\File as FileObject;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\Mime\MimeTypes;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class FileValidator extends ConstraintValidator
+{
+ public const KB_BYTES = 1000;
+ public const MB_BYTES = 1000000;
+ public const KIB_BYTES = 1024;
+ public const MIB_BYTES = 1048576;
+
+ private const SUFFICES = [
+ 1 => 'bytes',
+ self::KB_BYTES => 'kB',
+ self::MB_BYTES => 'MB',
+ self::KIB_BYTES => 'KiB',
+ self::MIB_BYTES => 'MiB',
+ ];
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof File) {
+ throw new UnexpectedTypeException($constraint, File::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if ($value instanceof UploadedFile && !$value->isValid()) {
+ switch ($value->getError()) {
+ case \UPLOAD_ERR_INI_SIZE:
+ $iniLimitSize = UploadedFile::getMaxFilesize();
+ if ($constraint->maxSize && $constraint->maxSize < $iniLimitSize) {
+ $limitInBytes = $constraint->maxSize;
+ $binaryFormat = $constraint->binaryFormat;
+ } else {
+ $limitInBytes = $iniLimitSize;
+ $binaryFormat = $constraint->binaryFormat ?? true;
+ }
+
+ [, $limitAsString, $suffix] = $this->factorizeSizes(0, $limitInBytes, $binaryFormat);
+ $this->context->buildViolation($constraint->uploadIniSizeErrorMessage)
+ ->setParameter('{{ limit }}', $limitAsString)
+ ->setParameter('{{ suffix }}', $suffix)
+ ->setCode((string) \UPLOAD_ERR_INI_SIZE)
+ ->addViolation();
+
+ return;
+ case \UPLOAD_ERR_FORM_SIZE:
+ $this->context->buildViolation($constraint->uploadFormSizeErrorMessage)
+ ->setCode((string) \UPLOAD_ERR_FORM_SIZE)
+ ->addViolation();
+
+ return;
+ case \UPLOAD_ERR_PARTIAL:
+ $this->context->buildViolation($constraint->uploadPartialErrorMessage)
+ ->setCode((string) \UPLOAD_ERR_PARTIAL)
+ ->addViolation();
+
+ return;
+ case \UPLOAD_ERR_NO_FILE:
+ $this->context->buildViolation($constraint->uploadNoFileErrorMessage)
+ ->setCode((string) \UPLOAD_ERR_NO_FILE)
+ ->addViolation();
+
+ return;
+ case \UPLOAD_ERR_NO_TMP_DIR:
+ $this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage)
+ ->setCode((string) \UPLOAD_ERR_NO_TMP_DIR)
+ ->addViolation();
+
+ return;
+ case \UPLOAD_ERR_CANT_WRITE:
+ $this->context->buildViolation($constraint->uploadCantWriteErrorMessage)
+ ->setCode((string) \UPLOAD_ERR_CANT_WRITE)
+ ->addViolation();
+
+ return;
+ case \UPLOAD_ERR_EXTENSION:
+ $this->context->buildViolation($constraint->uploadExtensionErrorMessage)
+ ->setCode((string) \UPLOAD_ERR_EXTENSION)
+ ->addViolation();
+
+ return;
+ default:
+ $this->context->buildViolation($constraint->uploadErrorMessage)
+ ->setCode((string) $value->getError())
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ if (!\is_scalar($value) && !$value instanceof FileObject && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $path = $value instanceof FileObject ? $value->getPathname() : (string) $value;
+
+ if (!is_file($path)) {
+ $this->context->buildViolation($constraint->notFoundMessage)
+ ->setParameter('{{ file }}', $this->formatValue($path))
+ ->setCode(File::NOT_FOUND_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (!is_readable($path)) {
+ $this->context->buildViolation($constraint->notReadableMessage)
+ ->setParameter('{{ file }}', $this->formatValue($path))
+ ->setCode(File::NOT_READABLE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $sizeInBytes = filesize($path);
+ $basename = $value instanceof UploadedFile ? $value->getClientOriginalName() : basename($path);
+
+ if ($constraint->filenameMaxLength && $constraint->filenameMaxLength < $filenameLength = \strlen($basename)) {
+ $this->context->buildViolation($constraint->filenameTooLongMessage)
+ ->setParameter('{{ filename_max_length }}', $this->formatValue($constraint->filenameMaxLength))
+ ->setCode(File::FILENAME_TOO_LONG)
+ ->setPlural($constraint->filenameMaxLength)
+ ->addViolation();
+
+ return;
+ }
+
+ if (0 === $sizeInBytes) {
+ $this->context->buildViolation($constraint->disallowEmptyMessage)
+ ->setParameter('{{ file }}', $this->formatValue($path))
+ ->setParameter('{{ name }}', $this->formatValue($basename))
+ ->setCode(File::EMPTY_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if ($constraint->maxSize) {
+ $limitInBytes = $constraint->maxSize;
+
+ if ($sizeInBytes > $limitInBytes) {
+ [$sizeAsString, $limitAsString, $suffix] = $this->factorizeSizes($sizeInBytes, $limitInBytes, $constraint->binaryFormat);
+ $this->context->buildViolation($constraint->maxSizeMessage)
+ ->setParameter('{{ file }}', $this->formatValue($path))
+ ->setParameter('{{ size }}', $sizeAsString)
+ ->setParameter('{{ limit }}', $limitAsString)
+ ->setParameter('{{ suffix }}', $suffix)
+ ->setParameter('{{ name }}', $this->formatValue($basename))
+ ->setCode(File::TOO_LARGE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ $mimeTypes = (array) $constraint->mimeTypes;
+
+ if ($constraint->extensions) {
+ $fileExtension = strtolower(pathinfo($basename, \PATHINFO_EXTENSION));
+
+ $found = false;
+ $normalizedExtensions = [];
+ foreach ((array) $constraint->extensions as $k => $v) {
+ if (!\is_string($k)) {
+ $k = $v;
+ $v = null;
+ }
+
+ $normalizedExtensions[] = $k;
+
+ if ($fileExtension !== $k) {
+ continue;
+ }
+
+ $found = true;
+ if (null === $v) {
+ if (!class_exists(MimeTypes::class)) {
+ throw new LogicException('You cannot validate the mime-type of files as the Mime component is not installed. Try running "composer require symfony/mime".');
+ }
+
+ $mimeTypesHelper = MimeTypes::getDefault();
+ $v = $mimeTypesHelper->getMimeTypes($k);
+ }
+
+ $mimeTypes = $mimeTypes ? array_intersect($v, $mimeTypes) : (array) $v;
+ break;
+ }
+
+ if (!$found) {
+ $this->context->buildViolation($constraint->extensionsMessage)
+ ->setParameter('{{ file }}', $this->formatValue($path))
+ ->setParameter('{{ extension }}', $this->formatValue($fileExtension))
+ ->setParameter('{{ extensions }}', $this->formatValues($normalizedExtensions))
+ ->setParameter('{{ name }}', $this->formatValue($basename))
+ ->setCode(File::INVALID_EXTENSION_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if ($mimeTypes) {
+ if ($value instanceof FileObject) {
+ $mime = $value->getMimeType();
+ } elseif (isset($mimeTypesHelper) || class_exists(MimeTypes::class)) {
+ $mime = ($mimeTypesHelper ?? MimeTypes::getDefault())->guessMimeType($path);
+ } elseif (!class_exists(FileObject::class)) {
+ throw new LogicException('You cannot validate the mime-type of files as the Mime component is not installed. Try running "composer require symfony/mime".');
+ } else {
+ $mime = (new FileObject($value))->getMimeType();
+ }
+
+ foreach ($mimeTypes as $mimeType) {
+ if ($mimeType === $mime) {
+ return;
+ }
+
+ if ($discrete = strstr($mimeType, '/*', true)) {
+ if (strstr($mime, '/', true) === $discrete) {
+ return;
+ }
+ }
+ }
+
+ $this->context->buildViolation($constraint->mimeTypesMessage)
+ ->setParameter('{{ file }}', $this->formatValue($path))
+ ->setParameter('{{ type }}', $this->formatValue($mime))
+ ->setParameter('{{ types }}', $this->formatValues($mimeTypes))
+ ->setParameter('{{ name }}', $this->formatValue($basename))
+ ->setCode(File::INVALID_MIME_TYPE_ERROR)
+ ->addViolation();
+ }
+ }
+
+ private static function moreDecimalsThan(string $double, int $numberOfDecimals): bool
+ {
+ return \strlen($double) > \strlen(round($double, $numberOfDecimals));
+ }
+
+ /**
+ * Convert the limit to the smallest possible number
+ * (i.e. try "MB", then "kB", then "bytes").
+ */
+ private function factorizeSizes(int $size, int|float $limit, bool $binaryFormat): array
+ {
+ if ($binaryFormat) {
+ $coef = self::MIB_BYTES;
+ $coefFactor = self::KIB_BYTES;
+ } else {
+ $coef = self::MB_BYTES;
+ $coefFactor = self::KB_BYTES;
+ }
+
+ // If $limit < $coef, $limitAsString could be < 1 with less than 3 decimals.
+ // In this case, we would end up displaying an allowed size < 1 (eg: 0.1 MB).
+ // It looks better to keep on factorizing (to display 100 kB for example).
+ while ($limit < $coef) {
+ $coef /= $coefFactor;
+ }
+
+ $limitAsString = (string) ($limit / $coef);
+
+ // Restrict the limit to 2 decimals (without rounding! we
+ // need the precise value)
+ while (self::moreDecimalsThan($limitAsString, 2)) {
+ $coef /= $coefFactor;
+ $limitAsString = (string) ($limit / $coef);
+ }
+
+ // Convert size to the same measure, but round to 2 decimals
+ $sizeAsString = (string) round($size / $coef, 2);
+
+ // If the size and limit produce the same string output
+ // (due to rounding), reduce the coefficient
+ while ($sizeAsString === $limitAsString) {
+ $coef /= $coefFactor;
+ $limitAsString = (string) ($limit / $coef);
+ $sizeAsString = (string) round($size / $coef, 2);
+ }
+
+ return [$sizeAsString, $limitAsString, self::SUFFICES[$coef]];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/GreaterThan.php b/lib/symfony/validator/Constraints/GreaterThan.php
new file mode 100644
index 0000000000..ce56f1ac1c
--- /dev/null
+++ b/lib/symfony/validator/Constraints/GreaterThan.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class GreaterThan extends AbstractComparison
+{
+ public const TOO_LOW_ERROR = '778b7ae0-84d3-481a-9dec-35fdb64b1d78';
+
+ protected const ERROR_NAMES = [
+ self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be greater than {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/GreaterThanOrEqual.php b/lib/symfony/validator/Constraints/GreaterThanOrEqual.php
new file mode 100644
index 0000000000..c962f7964f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/GreaterThanOrEqual.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class GreaterThanOrEqual extends AbstractComparison
+{
+ public const TOO_LOW_ERROR = 'ea4e51d1-3342-48bd-87f1-9e672cd90cad';
+
+ protected const ERROR_NAMES = [
+ self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be greater than or equal to {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/GreaterThanOrEqualValidator.php b/lib/symfony/validator/Constraints/GreaterThanOrEqualValidator.php
new file mode 100644
index 0000000000..bcf80a82d4
--- /dev/null
+++ b/lib/symfony/validator/Constraints/GreaterThanOrEqualValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values are greater than or equal to the previous (>=).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class GreaterThanOrEqualValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return null === $value2 || $value1 >= $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return GreaterThanOrEqual::TOO_LOW_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/GreaterThanValidator.php b/lib/symfony/validator/Constraints/GreaterThanValidator.php
new file mode 100644
index 0000000000..1e8734fab3
--- /dev/null
+++ b/lib/symfony/validator/Constraints/GreaterThanValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values are greater than the previous (>).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class GreaterThanValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return null === $value2 || $value1 > $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return GreaterThan::TOO_LOW_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/GroupSequence.php b/lib/symfony/validator/Constraints/GroupSequence.php
new file mode 100644
index 0000000000..8c91b4de2f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/GroupSequence.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * A sequence of validation groups.
+ *
+ * When validating a group sequence, each group will only be validated if all
+ * of the previous groups in the sequence succeeded. For example:
+ *
+ * $validator->validate($address, null, new GroupSequence(['Basic', 'Strict']));
+ *
+ * In the first step, all constraints that belong to the group "Basic" will be
+ * validated. If none of the constraints fail, the validator will then validate
+ * the constraints in group "Strict". This is useful, for example, if "Strict"
+ * contains expensive checks that require a lot of CPU or slow, external
+ * services. You usually don't want to run expensive checks if any of the cheap
+ * checks fail.
+ *
+ * When adding metadata to a class, you can override the "Default" group of
+ * that class with a group sequence:
+ * #[GroupSequence(['Address', 'Strict'])]
+ * class Address
+ * {
+ * // ...
+ * }
+ *
+ * Whenever you validate that object in the "Default" group, the group sequence
+ * will be validated:
+ *
+ * $validator->validate($address);
+ *
+ * If you want to execute the constraints of the "Default" group for a class
+ * with an overridden default group, pass the class name as group name instead:
+ *
+ * $validator->validate($address, null, "Address")
+ *
+ * @Annotation
+ * @Target({"CLASS", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class GroupSequence
+{
+ /**
+ * The groups in the sequence.
+ *
+ * @var array
+ */
+ public $groups;
+
+ /**
+ * The group in which cascaded objects are validated when validating
+ * this sequence.
+ *
+ * By default, cascaded objects are validated in each of the groups of
+ * the sequence.
+ *
+ * If a class has a group sequence attached, that sequence replaces the
+ * "Default" group. When validating that class in the "Default" group, the
+ * group sequence is used instead, but still the "Default" group should be
+ * cascaded to other objects.
+ *
+ * @var string|GroupSequence
+ */
+ public $cascadedGroup;
+
+ /**
+ * Creates a new group sequence.
+ *
+ * @param array $groups The groups in the sequence
+ */
+ public function __construct(array $groups)
+ {
+ $this->groups = $groups['value'] ?? $groups;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/GroupSequenceProvider.php b/lib/symfony/validator/Constraints/GroupSequenceProvider.php
new file mode 100644
index 0000000000..842bfd4684
--- /dev/null
+++ b/lib/symfony/validator/Constraints/GroupSequenceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
+use Symfony\Component\Validator\Attribute\HasNamedArguments;
+
+/**
+ * Attribute to define a group sequence provider.
+ *
+ * @Annotation
+ * @NamedArgumentConstructor
+ * @Target({"CLASS", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class GroupSequenceProvider
+{
+ #[HasNamedArguments]
+ public function __construct(public ?string $provider = null)
+ {
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Hostname.php b/lib/symfony/validator/Constraints/Hostname.php
new file mode 100644
index 0000000000..79e1c87c27
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Hostname.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Dmitrii Poddubnyi
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Hostname extends Constraint
+{
+ public const INVALID_HOSTNAME_ERROR = '7057ffdb-0af4-4f7e-bd5e-e9acfa6d7a2d';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_HOSTNAME_ERROR => 'INVALID_HOSTNAME_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid hostname.';
+ public $requireTld = true;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?bool $requireTld = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->requireTld = $requireTld ?? $this->requireTld;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/HostnameValidator.php b/lib/symfony/validator/Constraints/HostnameValidator.php
new file mode 100644
index 0000000000..8b0fa60e20
--- /dev/null
+++ b/lib/symfony/validator/Constraints/HostnameValidator.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Dmitrii Poddubnyi
+ */
+class HostnameValidator extends ConstraintValidator
+{
+ /**
+ * https://tools.ietf.org/html/rfc2606.
+ */
+ private const RESERVED_TLDS = [
+ 'example',
+ 'invalid',
+ 'localhost',
+ 'test',
+ ];
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Hostname) {
+ throw new UnexpectedTypeException($constraint, Hostname::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+ if ('' === $value) {
+ return;
+ }
+ if (!$this->isValid($value) || ($constraint->requireTld && !$this->hasValidTld($value))) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Hostname::INVALID_HOSTNAME_ERROR)
+ ->addViolation();
+ }
+ }
+
+ private function isValid(string $domain): bool
+ {
+ return false !== filter_var($domain, \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME);
+ }
+
+ private function hasValidTld(string $domain): bool
+ {
+ return str_contains($domain, '.') && !\in_array(substr($domain, strrpos($domain, '.') + 1), self::RESERVED_TLDS, true);
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Iban.php b/lib/symfony/validator/Constraints/Iban.php
new file mode 100644
index 0000000000..16f557f12e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Iban.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Manuel Reinhard
+ * @author Michael Schummel
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Iban extends Constraint
+{
+ public const INVALID_COUNTRY_CODE_ERROR = 'de78ee2c-bd50-44e2-aec8-3d8228aeadb9';
+ public const INVALID_CHARACTERS_ERROR = '8d3d85e4-784f-4719-a5bc-d9e40d45a3a5';
+ public const CHECKSUM_FAILED_ERROR = 'b9401321-f9bf-4dcb-83c1-f31094440795';
+ public const INVALID_FORMAT_ERROR = 'c8d318f1-2ecc-41ba-b983-df70d225cf5a';
+ public const NOT_SUPPORTED_COUNTRY_CODE_ERROR = 'e2c259f3-4b46-48e6-b72e-891658158ec8';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_COUNTRY_CODE_ERROR => 'INVALID_COUNTRY_CODE_ERROR',
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
+ self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
+ self::NOT_SUPPORTED_COUNTRY_CODE_ERROR => 'NOT_SUPPORTED_COUNTRY_CODE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This is not a valid International Bank Account Number (IBAN).';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IbanValidator.php b/lib/symfony/validator/Constraints/IbanValidator.php
new file mode 100644
index 0000000000..13d91315b5
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IbanValidator.php
@@ -0,0 +1,301 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Manuel Reinhard
+ * @author Michael Schummel
+ * @author Bernhard Schussek
+ */
+class IbanValidator extends ConstraintValidator
+{
+ /**
+ * IBAN country specific formats.
+ *
+ * The first 2 characters from an IBAN format are the two-character ISO country code.
+ * The following 2 characters represent the check digits calculated from the rest of the IBAN characters.
+ * The rest are up to thirty alphanumeric characters for
+ * a BBAN (Basic Bank Account Number) which has a fixed length per country and,
+ * included within it, a bank identifier with a fixed position and a fixed length per country
+ *
+ * @see Resources/bin/sync-iban-formats.php
+ * @see https://www.swift.com/swift-resource/11971/download?language=en
+ * @see https://en.wikipedia.org/wiki/International_Bank_Account_Number
+ */
+ private const FORMATS = [
+ // auto-generated
+ 'AD' => 'AD\d{2}\d{4}\d{4}[\dA-Z]{12}', // Andorra
+ 'AE' => 'AE\d{2}\d{3}\d{16}', // United Arab Emirates (The)
+ 'AL' => 'AL\d{2}\d{8}[\dA-Z]{16}', // Albania
+ 'AO' => 'AO\d{2}\d{21}', // Angola
+ 'AT' => 'AT\d{2}\d{5}\d{11}', // Austria
+ 'AX' => 'FI\d{2}\d{3}\d{11}', // Finland
+ 'AZ' => 'AZ\d{2}[A-Z]{4}[\dA-Z]{20}', // Azerbaijan
+ 'BA' => 'BA\d{2}\d{3}\d{3}\d{8}\d{2}', // Bosnia and Herzegovina
+ 'BE' => 'BE\d{2}\d{3}\d{7}\d{2}', // Belgium
+ 'BF' => 'BF\d{2}[\dA-Z]{2}\d{22}', // Burkina Faso
+ 'BG' => 'BG\d{2}[A-Z]{4}\d{4}\d{2}[\dA-Z]{8}', // Bulgaria
+ 'BH' => 'BH\d{2}[A-Z]{4}[\dA-Z]{14}', // Bahrain
+ 'BI' => 'BI\d{2}\d{5}\d{5}\d{11}\d{2}', // Burundi
+ 'BJ' => 'BJ\d{2}[\dA-Z]{2}\d{22}', // Benin
+ 'BL' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'BR' => 'BR\d{2}\d{8}\d{5}\d{10}[A-Z]{1}[\dA-Z]{1}', // Brazil
+ 'BY' => 'BY\d{2}[\dA-Z]{4}\d{4}[\dA-Z]{16}', // Republic of Belarus
+ 'CF' => 'CF\d{2}\d{23}', // Central African Republic
+ 'CG' => 'CG\d{2}\d{23}', // Congo, Republic of the
+ 'CH' => 'CH\d{2}\d{5}[\dA-Z]{12}', // Switzerland
+ 'CI' => 'CI\d{2}[A-Z]{1}\d{23}', // Côte d'Ivoire
+ 'CM' => 'CM\d{2}\d{23}', // Cameroon
+ 'CR' => 'CR\d{2}\d{4}\d{14}', // Costa Rica
+ 'CV' => 'CV\d{2}\d{21}', // Cabo Verde
+ 'CY' => 'CY\d{2}\d{3}\d{5}[\dA-Z]{16}', // Cyprus
+ 'CZ' => 'CZ\d{2}\d{4}\d{6}\d{10}', // Czechia
+ 'DE' => 'DE\d{2}\d{8}\d{10}', // Germany
+ 'DJ' => 'DJ\d{2}\d{5}\d{5}\d{11}\d{2}', // Djibouti
+ 'DK' => 'DK\d{2}\d{4}\d{9}\d{1}', // Denmark
+ 'DO' => 'DO\d{2}[\dA-Z]{4}\d{20}', // Dominican Republic
+ 'DZ' => 'DZ\d{2}\d{22}', // Algeria
+ 'EE' => 'EE\d{2}\d{2}\d{14}', // Estonia
+ 'EG' => 'EG\d{2}\d{4}\d{4}\d{17}', // Egypt
+ 'ES' => 'ES\d{2}\d{4}\d{4}\d{1}\d{1}\d{10}', // Spain
+ 'FI' => 'FI\d{2}\d{3}\d{11}', // Finland
+ 'FK' => 'FK\d{2}[A-Z]{2}\d{12}', // Falkland Islands
+ 'FO' => 'FO\d{2}\d{4}\d{9}\d{1}', // Faroe Islands
+ 'FR' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'GA' => 'GA\d{2}\d{23}', // Gabon
+ 'GB' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom
+ 'GE' => 'GE\d{2}[A-Z]{2}\d{16}', // Georgia
+ 'GF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'GG' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom
+ 'GI' => 'GI\d{2}[A-Z]{4}[\dA-Z]{15}', // Gibraltar
+ 'GL' => 'GL\d{2}\d{4}\d{9}\d{1}', // Greenland
+ 'GP' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'GQ' => 'GQ\d{2}\d{23}', // Equatorial Guinea
+ 'GR' => 'GR\d{2}\d{3}\d{4}[\dA-Z]{16}', // Greece
+ 'GT' => 'GT\d{2}[\dA-Z]{4}[\dA-Z]{20}', // Guatemala
+ 'GW' => 'GW\d{2}[\dA-Z]{2}\d{19}', // Guinea-Bissau
+ 'HN' => 'HN\d{2}[A-Z]{4}\d{20}', // Honduras
+ 'HR' => 'HR\d{2}\d{7}\d{10}', // Croatia
+ 'HU' => 'HU\d{2}\d{3}\d{4}\d{1}\d{15}\d{1}', // Hungary
+ 'IE' => 'IE\d{2}[A-Z]{4}\d{6}\d{8}', // Ireland
+ 'IL' => 'IL\d{2}\d{3}\d{3}\d{13}', // Israel
+ 'IM' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom
+ 'IQ' => 'IQ\d{2}[A-Z]{4}\d{3}\d{12}', // Iraq
+ 'IR' => 'IR\d{2}\d{22}', // Iran
+ 'IS' => 'IS\d{2}\d{4}\d{2}\d{6}\d{10}', // Iceland
+ 'IT' => 'IT\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // Italy
+ 'JE' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom
+ 'JO' => 'JO\d{2}[A-Z]{4}\d{4}[\dA-Z]{18}', // Jordan
+ 'KM' => 'KM\d{2}\d{23}', // Comoros
+ 'KW' => 'KW\d{2}[A-Z]{4}[\dA-Z]{22}', // Kuwait
+ 'KZ' => 'KZ\d{2}\d{3}[\dA-Z]{13}', // Kazakhstan
+ 'LB' => 'LB\d{2}\d{4}[\dA-Z]{20}', // Lebanon
+ 'LC' => 'LC\d{2}[A-Z]{4}[\dA-Z]{24}', // Saint Lucia
+ 'LI' => 'LI\d{2}\d{5}[\dA-Z]{12}', // Liechtenstein
+ 'LT' => 'LT\d{2}\d{5}\d{11}', // Lithuania
+ 'LU' => 'LU\d{2}\d{3}[\dA-Z]{13}', // Luxembourg
+ 'LV' => 'LV\d{2}[A-Z]{4}[\dA-Z]{13}', // Latvia
+ 'LY' => 'LY\d{2}\d{3}\d{3}\d{15}', // Libya
+ 'MA' => 'MA\d{2}\d{24}', // Morocco
+ 'MC' => 'MC\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Monaco
+ 'MD' => 'MD\d{2}[\dA-Z]{2}[\dA-Z]{18}', // Moldova
+ 'ME' => 'ME\d{2}\d{3}\d{13}\d{2}', // Montenegro
+ 'MF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'MG' => 'MG\d{2}\d{23}', // Madagascar
+ 'MK' => 'MK\d{2}\d{3}[\dA-Z]{10}\d{2}', // Macedonia
+ 'ML' => 'ML\d{2}[\dA-Z]{2}\d{22}', // Mali
+ 'MN' => 'MN\d{2}\d{4}\d{12}', // Mongolia
+ 'MQ' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'MR' => 'MR\d{2}\d{5}\d{5}\d{11}\d{2}', // Mauritania
+ 'MT' => 'MT\d{2}[A-Z]{4}\d{5}[\dA-Z]{18}', // Malta
+ 'MU' => 'MU\d{2}[A-Z]{4}\d{2}\d{2}\d{12}\d{3}[A-Z]{3}', // Mauritius
+ 'MZ' => 'MZ\d{2}\d{21}', // Mozambique
+ 'NC' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'NE' => 'NE\d{2}[A-Z]{2}\d{22}', // Niger
+ 'NI' => 'NI\d{2}[A-Z]{4}\d{20}', // Nicaragua
+ 'NL' => 'NL\d{2}[A-Z]{4}\d{10}', // Netherlands (The)
+ 'NO' => 'NO\d{2}\d{4}\d{6}\d{1}', // Norway
+ 'OM' => 'OM\d{2}\d{3}[\dA-Z]{16}', // Oman
+ 'PF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'PK' => 'PK\d{2}[A-Z]{4}[\dA-Z]{16}', // Pakistan
+ 'PL' => 'PL\d{2}\d{8}\d{16}', // Poland
+ 'PM' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'PS' => 'PS\d{2}[A-Z]{4}[\dA-Z]{21}', // Palestine, State of
+ 'PT' => 'PT\d{2}\d{4}\d{4}\d{11}\d{2}', // Portugal
+ 'QA' => 'QA\d{2}[A-Z]{4}[\dA-Z]{21}', // Qatar
+ 'RE' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'RO' => 'RO\d{2}[A-Z]{4}[\dA-Z]{16}', // Romania
+ 'RS' => 'RS\d{2}\d{3}\d{13}\d{2}', // Serbia
+ 'RU' => 'RU\d{2}\d{9}\d{5}[\dA-Z]{15}', // Russia
+ 'SA' => 'SA\d{2}\d{2}[\dA-Z]{18}', // Saudi Arabia
+ 'SC' => 'SC\d{2}[A-Z]{4}\d{2}\d{2}\d{16}[A-Z]{3}', // Seychelles
+ 'SD' => 'SD\d{2}\d{2}\d{12}', // Sudan
+ 'SE' => 'SE\d{2}\d{3}\d{16}\d{1}', // Sweden
+ 'SI' => 'SI\d{2}\d{5}\d{8}\d{2}', // Slovenia
+ 'SK' => 'SK\d{2}\d{4}\d{6}\d{10}', // Slovakia
+ 'SM' => 'SM\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // San Marino
+ 'SN' => 'SN\d{2}[A-Z]{2}\d{22}', // Senegal
+ 'SO' => 'SO\d{2}\d{4}\d{3}\d{12}', // Somalia
+ 'ST' => 'ST\d{2}\d{8}\d{11}\d{2}', // Sao Tome and Principe
+ 'SV' => 'SV\d{2}[A-Z]{4}\d{20}', // El Salvador
+ 'TD' => 'TD\d{2}\d{23}', // Chad
+ 'TF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'TG' => 'TG\d{2}[A-Z]{2}\d{22}', // Togo
+ 'TL' => 'TL\d{2}\d{3}\d{14}\d{2}', // Timor-Leste
+ 'TN' => 'TN\d{2}\d{2}\d{3}\d{13}\d{2}', // Tunisia
+ 'TR' => 'TR\d{2}\d{5}\d{1}[\dA-Z]{16}', // Turkey
+ 'UA' => 'UA\d{2}\d{6}[\dA-Z]{19}', // Ukraine
+ 'VA' => 'VA\d{2}\d{3}\d{15}', // Vatican City State
+ 'VG' => 'VG\d{2}[A-Z]{4}\d{16}', // Virgin Islands
+ 'WF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ 'XK' => 'XK\d{2}\d{4}\d{10}\d{2}', // Kosovo
+ 'YE' => 'YE\d{2}[A-Z]{4}\d{4}[\dA-Z]{18}', // Yemen
+ 'YT' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
+ ];
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Iban) {
+ throw new UnexpectedTypeException($constraint, Iban::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ // Remove spaces and convert to uppercase
+ $canonicalized = str_replace(' ', '', strtoupper($value));
+
+ // The IBAN must contain only digits and characters...
+ if (!ctype_alnum($canonicalized)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Iban::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // ...start with a two-letter country code
+ $countryCode = substr($canonicalized, 0, 2);
+
+ if (!ctype_alpha($countryCode)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Iban::INVALID_COUNTRY_CODE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // ...have a format available
+ if (!\array_key_exists($countryCode, self::FORMATS)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Iban::NOT_SUPPORTED_COUNTRY_CODE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // ...and have a valid format
+ if (!preg_match('/^'.self::FORMATS[$countryCode].'$/', $canonicalized)
+ ) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Iban::INVALID_FORMAT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // Check digits should always between 2 and 98
+ // A ECBS document (https://www.ecbs.org/Download/EBS204_V3.PDF) replicates part of the ISO/IEC 7064:2003 standard as a method for generating check digits in the range 02 to 98.
+ $checkDigits = (int) substr($canonicalized, 2, 2);
+ if ($checkDigits < 2 || $checkDigits > 98) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Iban::CHECKSUM_FAILED_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // Move the first four characters to the end
+ // e.g. CH93 0076 2011 6238 5295 7
+ // -> 0076 2011 6238 5295 7 CH93
+ $canonicalized = substr($canonicalized, 4).substr($canonicalized, 0, 4);
+
+ // Convert all remaining letters to their ordinals
+ // The result is an integer, which is too large for PHP's int
+ // data type, so we store it in a string instead.
+ // e.g. 0076 2011 6238 5295 7 CH93
+ // -> 0076 2011 6238 5295 7 121893
+ $checkSum = self::toBigInt($canonicalized);
+
+ // Do a modulo-97 operation on the large integer
+ // We cannot use PHP's modulo operator, so we calculate the
+ // modulo step-wisely instead
+ if (1 !== self::bigModulo97($checkSum)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Iban::CHECKSUM_FAILED_ERROR)
+ ->addViolation();
+ }
+ }
+
+ private static function toBigInt(string $string): string
+ {
+ $chars = str_split($string);
+ $bigInt = '';
+
+ foreach ($chars as $char) {
+ // Convert uppercase characters to ordinals, starting with 10 for "A"
+ if (ctype_upper($char)) {
+ $bigInt .= (\ord($char) - 55);
+
+ continue;
+ }
+
+ // Simply append digits
+ $bigInt .= $char;
+ }
+
+ return $bigInt;
+ }
+
+ private static function bigModulo97(string $bigInt): int
+ {
+ $parts = str_split($bigInt, 7);
+ $rest = 0;
+
+ foreach ($parts as $part) {
+ $rest = ($rest.$part) % 97;
+ }
+
+ return $rest;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IdenticalTo.php b/lib/symfony/validator/Constraints/IdenticalTo.php
new file mode 100644
index 0000000000..50ec5e1297
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IdenticalTo.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class IdenticalTo extends AbstractComparison
+{
+ public const NOT_IDENTICAL_ERROR = '2a8cc50f-58a2-4536-875e-060a2ce69ed5';
+
+ protected const ERROR_NAMES = [
+ self::NOT_IDENTICAL_ERROR => 'NOT_IDENTICAL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be identical to {{ compared_value_type }} {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/IdenticalToValidator.php b/lib/symfony/validator/Constraints/IdenticalToValidator.php
new file mode 100644
index 0000000000..0f5d506780
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IdenticalToValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values are identical (===).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class IdenticalToValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return $value1 === $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return IdenticalTo::NOT_IDENTICAL_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Image.php b/lib/symfony/validator/Constraints/Image.php
new file mode 100644
index 0000000000..8fafa5044f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Image.php
@@ -0,0 +1,207 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Benjamin Dulau
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Image extends File
+{
+ public const SIZE_NOT_DETECTED_ERROR = '6d55c3f4-e58e-4fe3-91ee-74b492199956';
+ public const TOO_WIDE_ERROR = '7f87163d-878f-47f5-99ba-a8eb723a1ab2';
+ public const TOO_NARROW_ERROR = '9afbd561-4f90-4a27-be62-1780fc43604a';
+ public const TOO_HIGH_ERROR = '7efae81c-4877-47ba-aa65-d01ccb0d4645';
+ public const TOO_LOW_ERROR = 'aef0cb6a-c07f-4894-bc08-1781420d7b4c';
+ public const TOO_FEW_PIXEL_ERROR = '1b06b97d-ae48-474e-978f-038a74854c43';
+ public const TOO_MANY_PIXEL_ERROR = 'ee0804e8-44db-4eac-9775-be91aaf72ce1';
+ public const RATIO_TOO_BIG_ERROR = '70cafca6-168f-41c9-8c8c-4e47a52be643';
+ public const RATIO_TOO_SMALL_ERROR = '59b8c6ef-bcf2-4ceb-afff-4642ed92f12e';
+ public const SQUARE_NOT_ALLOWED_ERROR = '5d41425b-facb-47f7-a55a-de9fbe45cb46';
+ public const LANDSCAPE_NOT_ALLOWED_ERROR = '6f895685-7cf2-4d65-b3da-9029c5581d88';
+ public const PORTRAIT_NOT_ALLOWED_ERROR = '65608156-77da-4c79-a88c-02ef6d18c782';
+ public const CORRUPTED_IMAGE_ERROR = '5d4163f3-648f-4e39-87fd-cc5ea7aad2d1';
+
+ // Include the mapping from the base class
+
+ protected const ERROR_NAMES = [
+ self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR',
+ self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR',
+ self::EMPTY_ERROR => 'EMPTY_ERROR',
+ self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
+ self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR',
+ self::FILENAME_TOO_LONG => 'FILENAME_TOO_LONG',
+ self::SIZE_NOT_DETECTED_ERROR => 'SIZE_NOT_DETECTED_ERROR',
+ self::TOO_WIDE_ERROR => 'TOO_WIDE_ERROR',
+ self::TOO_NARROW_ERROR => 'TOO_NARROW_ERROR',
+ self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
+ self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
+ self::TOO_FEW_PIXEL_ERROR => 'TOO_FEW_PIXEL_ERROR',
+ self::TOO_MANY_PIXEL_ERROR => 'TOO_MANY_PIXEL_ERROR',
+ self::RATIO_TOO_BIG_ERROR => 'RATIO_TOO_BIG_ERROR',
+ self::RATIO_TOO_SMALL_ERROR => 'RATIO_TOO_SMALL_ERROR',
+ self::SQUARE_NOT_ALLOWED_ERROR => 'SQUARE_NOT_ALLOWED_ERROR',
+ self::LANDSCAPE_NOT_ALLOWED_ERROR => 'LANDSCAPE_NOT_ALLOWED_ERROR',
+ self::PORTRAIT_NOT_ALLOWED_ERROR => 'PORTRAIT_NOT_ALLOWED_ERROR',
+ self::CORRUPTED_IMAGE_ERROR => 'CORRUPTED_IMAGE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $mimeTypes;
+ public $minWidth;
+ public $maxWidth;
+ public $maxHeight;
+ public $minHeight;
+ public $maxRatio;
+ public $minRatio;
+ public $minPixels;
+ public $maxPixels;
+ public $allowSquare = true;
+ public $allowLandscape = true;
+ public $allowPortrait = true;
+ public $detectCorrupted = false;
+
+ // The constant for a wrong MIME type is taken from the parent class.
+ public $mimeTypesMessage = 'This file is not a valid image.';
+ public $sizeNotDetectedMessage = 'The size of the image could not be detected.';
+ public $maxWidthMessage = 'The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.';
+ public $minWidthMessage = 'The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.';
+ public $maxHeightMessage = 'The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.';
+ public $minHeightMessage = 'The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.';
+ public $minPixelsMessage = 'The image has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.';
+ public $maxPixelsMessage = 'The image has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.';
+ public $maxRatioMessage = 'The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.';
+ public $minRatioMessage = 'The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.';
+ public $allowSquareMessage = 'The image is square ({{ width }}x{{ height }}px). Square images are not allowed.';
+ public $allowLandscapeMessage = 'The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.';
+ public $allowPortraitMessage = 'The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.';
+ public $corruptedMessage = 'The image file is corrupted.';
+
+ public function __construct(
+ ?array $options = null,
+ int|string|null $maxSize = null,
+ ?bool $binaryFormat = null,
+ ?array $mimeTypes = null,
+ ?int $filenameMaxLength = null,
+ ?int $minWidth = null,
+ ?int $maxWidth = null,
+ ?int $maxHeight = null,
+ ?int $minHeight = null,
+ int|float|null $maxRatio = null,
+ int|float|null $minRatio = null,
+ int|float|null $minPixels = null,
+ int|float|null $maxPixels = null,
+ ?bool $allowSquare = null,
+ ?bool $allowLandscape = null,
+ ?bool $allowPortrait = null,
+ ?bool $detectCorrupted = null,
+ ?string $notFoundMessage = null,
+ ?string $notReadableMessage = null,
+ ?string $maxSizeMessage = null,
+ ?string $mimeTypesMessage = null,
+ ?string $disallowEmptyMessage = null,
+ ?string $filenameTooLongMessage = null,
+ ?string $uploadIniSizeErrorMessage = null,
+ ?string $uploadFormSizeErrorMessage = null,
+ ?string $uploadPartialErrorMessage = null,
+ ?string $uploadNoFileErrorMessage = null,
+ ?string $uploadNoTmpDirErrorMessage = null,
+ ?string $uploadCantWriteErrorMessage = null,
+ ?string $uploadExtensionErrorMessage = null,
+ ?string $uploadErrorMessage = null,
+ ?string $sizeNotDetectedMessage = null,
+ ?string $maxWidthMessage = null,
+ ?string $minWidthMessage = null,
+ ?string $maxHeightMessage = null,
+ ?string $minHeightMessage = null,
+ ?string $minPixelsMessage = null,
+ ?string $maxPixelsMessage = null,
+ ?string $maxRatioMessage = null,
+ ?string $minRatioMessage = null,
+ ?string $allowSquareMessage = null,
+ ?string $allowLandscapeMessage = null,
+ ?string $allowPortraitMessage = null,
+ ?string $corruptedMessage = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array|string|null $extensions = null,
+ ?string $extensionsMessage = null,
+ ) {
+ parent::__construct(
+ $options,
+ $maxSize,
+ $binaryFormat,
+ $mimeTypes,
+ $filenameMaxLength,
+ $notFoundMessage,
+ $notReadableMessage,
+ $maxSizeMessage,
+ $mimeTypesMessage,
+ $disallowEmptyMessage,
+ $filenameTooLongMessage,
+ $uploadIniSizeErrorMessage,
+ $uploadFormSizeErrorMessage,
+ $uploadPartialErrorMessage,
+ $uploadNoFileErrorMessage,
+ $uploadNoTmpDirErrorMessage,
+ $uploadCantWriteErrorMessage,
+ $uploadExtensionErrorMessage,
+ $uploadErrorMessage,
+ $groups,
+ $payload,
+ $extensions,
+ $extensionsMessage,
+ );
+
+ $this->minWidth = $minWidth ?? $this->minWidth;
+ $this->maxWidth = $maxWidth ?? $this->maxWidth;
+ $this->maxHeight = $maxHeight ?? $this->maxHeight;
+ $this->minHeight = $minHeight ?? $this->minHeight;
+ $this->maxRatio = $maxRatio ?? $this->maxRatio;
+ $this->minRatio = $minRatio ?? $this->minRatio;
+ $this->minPixels = $minPixels ?? $this->minPixels;
+ $this->maxPixels = $maxPixels ?? $this->maxPixels;
+ $this->allowSquare = $allowSquare ?? $this->allowSquare;
+ $this->allowLandscape = $allowLandscape ?? $this->allowLandscape;
+ $this->allowPortrait = $allowPortrait ?? $this->allowPortrait;
+ $this->detectCorrupted = $detectCorrupted ?? $this->detectCorrupted;
+ $this->sizeNotDetectedMessage = $sizeNotDetectedMessage ?? $this->sizeNotDetectedMessage;
+ $this->maxWidthMessage = $maxWidthMessage ?? $this->maxWidthMessage;
+ $this->minWidthMessage = $minWidthMessage ?? $this->minWidthMessage;
+ $this->maxHeightMessage = $maxHeightMessage ?? $this->maxHeightMessage;
+ $this->minHeightMessage = $minHeightMessage ?? $this->minHeightMessage;
+ $this->minPixelsMessage = $minPixelsMessage ?? $this->minPixelsMessage;
+ $this->maxPixelsMessage = $maxPixelsMessage ?? $this->maxPixelsMessage;
+ $this->maxRatioMessage = $maxRatioMessage ?? $this->maxRatioMessage;
+ $this->minRatioMessage = $minRatioMessage ?? $this->minRatioMessage;
+ $this->allowSquareMessage = $allowSquareMessage ?? $this->allowSquareMessage;
+ $this->allowLandscapeMessage = $allowLandscapeMessage ?? $this->allowLandscapeMessage;
+ $this->allowPortraitMessage = $allowPortraitMessage ?? $this->allowPortraitMessage;
+ $this->corruptedMessage = $corruptedMessage ?? $this->corruptedMessage;
+
+ if (null === $this->mimeTypes && [] === $this->extensions) {
+ $this->mimeTypes = 'image/*';
+ }
+
+ if (!\in_array('image/*', (array) $this->mimeTypes, true) && !\array_key_exists('mimeTypesMessage', $options ?? []) && null === $mimeTypesMessage) {
+ $this->mimeTypesMessage = 'The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.';
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ImageValidator.php b/lib/symfony/validator/Constraints/ImageValidator.php
new file mode 100644
index 0000000000..0c2fd318ab
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ImageValidator.php
@@ -0,0 +1,235 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * Validates whether a value is a valid image file and is valid
+ * against minWidth, maxWidth, minHeight and maxHeight constraints.
+ *
+ * @author Benjamin Dulau
+ * @author Bernhard Schussek
+ */
+class ImageValidator extends FileValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Image) {
+ throw new UnexpectedTypeException($constraint, Image::class);
+ }
+
+ $violations = \count($this->context->getViolations());
+
+ parent::validate($value, $constraint);
+
+ $failed = \count($this->context->getViolations()) !== $violations;
+
+ if ($failed || null === $value || '' === $value) {
+ return;
+ }
+
+ if (null === $constraint->minWidth && null === $constraint->maxWidth
+ && null === $constraint->minHeight && null === $constraint->maxHeight
+ && null === $constraint->minPixels && null === $constraint->maxPixels
+ && null === $constraint->minRatio && null === $constraint->maxRatio
+ && $constraint->allowSquare && $constraint->allowLandscape && $constraint->allowPortrait
+ && !$constraint->detectCorrupted) {
+ return;
+ }
+
+ $size = @getimagesize($value);
+
+ if (empty($size) || (0 === $size[0]) || (0 === $size[1])) {
+ $this->context->buildViolation($constraint->sizeNotDetectedMessage)
+ ->setCode(Image::SIZE_NOT_DETECTED_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $width = $size[0];
+ $height = $size[1];
+
+ if ($constraint->minWidth) {
+ if (!ctype_digit((string) $constraint->minWidth)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum width.', $constraint->minWidth));
+ }
+
+ if ($width < $constraint->minWidth) {
+ $this->context->buildViolation($constraint->minWidthMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ min_width }}', $constraint->minWidth)
+ ->setCode(Image::TOO_NARROW_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ if ($constraint->maxWidth) {
+ if (!ctype_digit((string) $constraint->maxWidth)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum width.', $constraint->maxWidth));
+ }
+
+ if ($width > $constraint->maxWidth) {
+ $this->context->buildViolation($constraint->maxWidthMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ max_width }}', $constraint->maxWidth)
+ ->setCode(Image::TOO_WIDE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ if ($constraint->minHeight) {
+ if (!ctype_digit((string) $constraint->minHeight)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum height.', $constraint->minHeight));
+ }
+
+ if ($height < $constraint->minHeight) {
+ $this->context->buildViolation($constraint->minHeightMessage)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ min_height }}', $constraint->minHeight)
+ ->setCode(Image::TOO_LOW_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ if ($constraint->maxHeight) {
+ if (!ctype_digit((string) $constraint->maxHeight)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum height.', $constraint->maxHeight));
+ }
+
+ if ($height > $constraint->maxHeight) {
+ $this->context->buildViolation($constraint->maxHeightMessage)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ max_height }}', $constraint->maxHeight)
+ ->setCode(Image::TOO_HIGH_ERROR)
+ ->addViolation();
+ }
+ }
+
+ $pixels = $width * $height;
+
+ if (null !== $constraint->minPixels) {
+ if (!ctype_digit((string) $constraint->minPixels)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum amount of pixels.', $constraint->minPixels));
+ }
+
+ if ($pixels < $constraint->minPixels) {
+ $this->context->buildViolation($constraint->minPixelsMessage)
+ ->setParameter('{{ pixels }}', $pixels)
+ ->setParameter('{{ min_pixels }}', $constraint->minPixels)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ width }}', $width)
+ ->setCode(Image::TOO_FEW_PIXEL_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if (null !== $constraint->maxPixels) {
+ if (!ctype_digit((string) $constraint->maxPixels)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum amount of pixels.', $constraint->maxPixels));
+ }
+
+ if ($pixels > $constraint->maxPixels) {
+ $this->context->buildViolation($constraint->maxPixelsMessage)
+ ->setParameter('{{ pixels }}', $pixels)
+ ->setParameter('{{ max_pixels }}', $constraint->maxPixels)
+ ->setParameter('{{ height }}', $height)
+ ->setParameter('{{ width }}', $width)
+ ->setCode(Image::TOO_MANY_PIXEL_ERROR)
+ ->addViolation();
+ }
+ }
+
+ $ratio = round($width / $height, 2);
+
+ if (null !== $constraint->minRatio) {
+ if (!is_numeric((string) $constraint->minRatio)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum ratio.', $constraint->minRatio));
+ }
+
+ if ($ratio < round($constraint->minRatio, 2)) {
+ $this->context->buildViolation($constraint->minRatioMessage)
+ ->setParameter('{{ ratio }}', $ratio)
+ ->setParameter('{{ min_ratio }}', round($constraint->minRatio, 2))
+ ->setCode(Image::RATIO_TOO_SMALL_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if (null !== $constraint->maxRatio) {
+ if (!is_numeric((string) $constraint->maxRatio)) {
+ throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum ratio.', $constraint->maxRatio));
+ }
+
+ if ($ratio > round($constraint->maxRatio, 2)) {
+ $this->context->buildViolation($constraint->maxRatioMessage)
+ ->setParameter('{{ ratio }}', $ratio)
+ ->setParameter('{{ max_ratio }}', round($constraint->maxRatio, 2))
+ ->setCode(Image::RATIO_TOO_BIG_ERROR)
+ ->addViolation();
+ }
+ }
+
+ if (!$constraint->allowSquare && $width == $height) {
+ $this->context->buildViolation($constraint->allowSquareMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ height }}', $height)
+ ->setCode(Image::SQUARE_NOT_ALLOWED_ERROR)
+ ->addViolation();
+ }
+
+ if (!$constraint->allowLandscape && $width > $height) {
+ $this->context->buildViolation($constraint->allowLandscapeMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ height }}', $height)
+ ->setCode(Image::LANDSCAPE_NOT_ALLOWED_ERROR)
+ ->addViolation();
+ }
+
+ if (!$constraint->allowPortrait && $width < $height) {
+ $this->context->buildViolation($constraint->allowPortraitMessage)
+ ->setParameter('{{ width }}', $width)
+ ->setParameter('{{ height }}', $height)
+ ->setCode(Image::PORTRAIT_NOT_ALLOWED_ERROR)
+ ->addViolation();
+ }
+
+ if ($constraint->detectCorrupted) {
+ if (!\function_exists('imagecreatefromstring')) {
+ throw new LogicException('Corrupted images detection requires installed and enabled GD extension.');
+ }
+
+ $resource = @imagecreatefromstring(file_get_contents($value));
+
+ if (false === $resource) {
+ $this->context->buildViolation($constraint->corruptedMessage)
+ ->setCode(Image::CORRUPTED_IMAGE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Ip.php b/lib/symfony/validator/Constraints/Ip.php
new file mode 100644
index 0000000000..0ff522107b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Ip.php
@@ -0,0 +1,112 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+
+/**
+ * Validates that a value is a valid IP address.
+ *
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ * @author Joseph Bielawski
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Ip extends Constraint
+{
+ public const V4 = '4';
+ public const V6 = '6';
+ public const ALL = 'all';
+
+ // adds FILTER_FLAG_NO_PRIV_RANGE flag (skip private ranges)
+ public const V4_NO_PRIV = '4_no_priv';
+ public const V6_NO_PRIV = '6_no_priv';
+ public const ALL_NO_PRIV = 'all_no_priv';
+
+ // adds FILTER_FLAG_NO_RES_RANGE flag (skip reserved ranges)
+ public const V4_NO_RES = '4_no_res';
+ public const V6_NO_RES = '6_no_res';
+ public const ALL_NO_RES = 'all_no_res';
+
+ // adds FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE flags (skip both)
+ public const V4_ONLY_PUBLIC = '4_public';
+ public const V6_ONLY_PUBLIC = '6_public';
+ public const ALL_ONLY_PUBLIC = 'all_public';
+
+ public const INVALID_IP_ERROR = 'b1b427ae-9f6f-41b0-aa9b-84511fbb3c5b';
+
+ protected const VERSIONS = [
+ self::V4,
+ self::V6,
+ self::ALL,
+
+ self::V4_NO_PRIV,
+ self::V6_NO_PRIV,
+ self::ALL_NO_PRIV,
+
+ self::V4_NO_RES,
+ self::V6_NO_RES,
+ self::ALL_NO_RES,
+
+ self::V4_ONLY_PUBLIC,
+ self::V6_ONLY_PUBLIC,
+ self::ALL_ONLY_PUBLIC,
+ ];
+
+ protected const ERROR_NAMES = [
+ self::INVALID_IP_ERROR => 'INVALID_IP_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const VERSIONS instead
+ */
+ protected static $versions = self::VERSIONS;
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $version = self::V4;
+
+ public $message = 'This is not a valid IP address.';
+
+ /** @var callable|null */
+ public $normalizer;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $version = null,
+ ?string $message = null,
+ ?callable $normalizer = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->version = $version ?? $this->version;
+ $this->message = $message ?? $this->message;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+
+ if (!\in_array($this->version, self::$versions)) {
+ throw new ConstraintDefinitionException(\sprintf('The option "version" must be one of "%s".', implode('", "', self::$versions)));
+ }
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IpValidator.php b/lib/symfony/validator/Constraints/IpValidator.php
new file mode 100644
index 0000000000..2f71a88045
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IpValidator.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether a value is a valid IP address.
+ *
+ * @author Bernhard Schussek
+ * @author Joseph Bielawski
+ */
+class IpValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Ip) {
+ throw new UnexpectedTypeException($constraint, Ip::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (null !== $constraint->normalizer) {
+ $value = ($constraint->normalizer)($value);
+ }
+
+ $flag = match ($constraint->version) {
+ Ip::V4 => \FILTER_FLAG_IPV4,
+ Ip::V6 => \FILTER_FLAG_IPV6,
+ Ip::V4_NO_PRIV => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE,
+ Ip::V6_NO_PRIV => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE,
+ Ip::ALL_NO_PRIV => \FILTER_FLAG_NO_PRIV_RANGE,
+ Ip::V4_NO_RES => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_RES_RANGE,
+ Ip::V6_NO_RES => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_RES_RANGE,
+ Ip::ALL_NO_RES => \FILTER_FLAG_NO_RES_RANGE,
+ Ip::V4_ONLY_PUBLIC => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
+ Ip::V6_ONLY_PUBLIC => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
+ Ip::ALL_ONLY_PUBLIC => \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
+ default => 0,
+ };
+
+ if (!filter_var($value, \FILTER_VALIDATE_IP, $flag)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Ip::INVALID_IP_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsFalse.php b/lib/symfony/validator/Constraints/IsFalse.php
new file mode 100644
index 0000000000..ad6f64f1da
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsFalse.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class IsFalse extends Constraint
+{
+ public const NOT_FALSE_ERROR = 'd53a91b0-def3-426a-83d7-269da7ab4200';
+
+ protected const ERROR_NAMES = [
+ self::NOT_FALSE_ERROR => 'NOT_FALSE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be false.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options ?? [], $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsFalseValidator.php b/lib/symfony/validator/Constraints/IsFalseValidator.php
new file mode 100644
index 0000000000..76a24ad789
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsFalseValidator.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class IsFalseValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof IsFalse) {
+ throw new UnexpectedTypeException($constraint, IsFalse::class);
+ }
+
+ if (null === $value || false === $value || 0 === $value || '0' === $value) {
+ return;
+ }
+
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(IsFalse::NOT_FALSE_ERROR)
+ ->addViolation();
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsNull.php b/lib/symfony/validator/Constraints/IsNull.php
new file mode 100644
index 0000000000..11c05485e9
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsNull.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class IsNull extends Constraint
+{
+ public const NOT_NULL_ERROR = '60d2f30b-8cfa-4372-b155-9656634de120';
+
+ protected const ERROR_NAMES = [
+ self::NOT_NULL_ERROR => 'NOT_NULL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be null.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options ?? [], $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsNullValidator.php b/lib/symfony/validator/Constraints/IsNullValidator.php
new file mode 100644
index 0000000000..628aacf267
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsNullValidator.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class IsNullValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof IsNull) {
+ throw new UnexpectedTypeException($constraint, IsNull::class);
+ }
+
+ if (null !== $value) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(IsNull::NOT_NULL_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsTrue.php b/lib/symfony/validator/Constraints/IsTrue.php
new file mode 100644
index 0000000000..db2ac166f6
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsTrue.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class IsTrue extends Constraint
+{
+ public const NOT_TRUE_ERROR = '2beabf1c-54c0-4882-a928-05249b26e23b';
+
+ protected const ERROR_NAMES = [
+ self::NOT_TRUE_ERROR => 'NOT_TRUE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be true.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options ?? [], $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsTrueValidator.php b/lib/symfony/validator/Constraints/IsTrueValidator.php
new file mode 100644
index 0000000000..644bbf70cb
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsTrueValidator.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class IsTrueValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof IsTrue) {
+ throw new UnexpectedTypeException($constraint, IsTrue::class);
+ }
+
+ if (null === $value || true === $value || 1 === $value || '1' === $value) {
+ return;
+ }
+
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(IsTrue::NOT_TRUE_ERROR)
+ ->addViolation();
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Isbn.php b/lib/symfony/validator/Constraints/Isbn.php
new file mode 100644
index 0000000000..e27d6d9771
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Isbn.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author The Whole Life To Learn
+ * @author Manuel Reinhard
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Isbn extends Constraint
+{
+ public const ISBN_10 = 'isbn10';
+ public const ISBN_13 = 'isbn13';
+
+ public const TOO_SHORT_ERROR = '949acbb0-8ef5-43ed-a0e9-032dfd08ae45';
+ public const TOO_LONG_ERROR = '3171387d-f80a-47b3-bd6e-60598545316a';
+ public const INVALID_CHARACTERS_ERROR = '23d21cea-da99-453d-98b1-a7d916fbb339';
+ public const CHECKSUM_FAILED_ERROR = '2881c032-660f-46b6-8153-d352d9706640';
+ public const TYPE_NOT_RECOGNIZED_ERROR = 'fa54a457-f042-441f-89c4-066ee5bdd3e1';
+
+ protected const ERROR_NAMES = [
+ self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
+ self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
+ self::TYPE_NOT_RECOGNIZED_ERROR => 'TYPE_NOT_RECOGNIZED_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $isbn10Message = 'This value is not a valid ISBN-10.';
+ public $isbn13Message = 'This value is not a valid ISBN-13.';
+ public $bothIsbnMessage = 'This value is neither a valid ISBN-10 nor a valid ISBN-13.';
+ public $type;
+ public $message;
+
+ public function __construct(
+ string|array|null $type = null,
+ ?string $message = null,
+ ?string $isbn10Message = null,
+ ?string $isbn13Message = null,
+ ?string $bothIsbnMessage = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array $options = [],
+ ) {
+ if (\is_array($type)) {
+ $options = array_merge($type, $options);
+ } elseif (null !== $type) {
+ $options['value'] = $type;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->isbn10Message = $isbn10Message ?? $this->isbn10Message;
+ $this->isbn13Message = $isbn13Message ?? $this->isbn13Message;
+ $this->bothIsbnMessage = $bothIsbnMessage ?? $this->bothIsbnMessage;
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'type';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsbnValidator.php b/lib/symfony/validator/Constraints/IsbnValidator.php
new file mode 100644
index 0000000000..26e9d4a211
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsbnValidator.php
@@ -0,0 +1,193 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether the value is a valid ISBN-10 or ISBN-13.
+ *
+ * @author The Whole Life To Learn
+ * @author Manuel Reinhard
+ * @author Bernhard Schussek
+ *
+ * @see https://en.wikipedia.org/wiki/Isbn
+ */
+class IsbnValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Isbn) {
+ throw new UnexpectedTypeException($constraint, Isbn::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+ $canonical = str_replace('-', '', $value);
+
+ // Explicitly validate against ISBN-10
+ if (Isbn::ISBN_10 === $constraint->type) {
+ if (true !== ($code = $this->validateIsbn10($canonical))) {
+ $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode($code)
+ ->addViolation();
+ }
+
+ return;
+ }
+
+ // Explicitly validate against ISBN-13
+ if (Isbn::ISBN_13 === $constraint->type) {
+ if (true !== ($code = $this->validateIsbn13($canonical))) {
+ $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode($code)
+ ->addViolation();
+ }
+
+ return;
+ }
+
+ // Try both ISBNs
+
+ // First, try ISBN-10
+ $code = $this->validateIsbn10($canonical);
+
+ // The ISBN can only be an ISBN-13 if the value was too long for ISBN-10
+ if (Isbn::TOO_LONG_ERROR === $code) {
+ // Try ISBN-13 now
+ $code = $this->validateIsbn13($canonical);
+
+ // If too short, this means we have 11 or 12 digits
+ if (Isbn::TOO_SHORT_ERROR === $code) {
+ $code = Isbn::TYPE_NOT_RECOGNIZED_ERROR;
+ }
+ }
+
+ if (true !== $code) {
+ $this->context->buildViolation($this->getMessage($constraint))
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode($code)
+ ->addViolation();
+ }
+ }
+
+ /**
+ * @return string|bool
+ */
+ protected function validateIsbn10(string $isbn)
+ {
+ // Choose an algorithm so that ERROR_INVALID_CHARACTERS is preferred
+ // over ERROR_TOO_SHORT/ERROR_TOO_LONG
+ // Otherwise "0-45122-5244" passes, but "0-45122_5244" reports
+ // "too long"
+
+ // Error priority:
+ // 1. ERROR_INVALID_CHARACTERS
+ // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
+ // 3. ERROR_CHECKSUM_FAILED
+
+ $checkSum = 0;
+
+ for ($i = 0; $i < 10; ++$i) {
+ // If we test the length before the loop, we get an ERROR_TOO_SHORT
+ // when actually an ERROR_INVALID_CHARACTERS is wanted, e.g. for
+ // "0-45122_5244" (typo)
+ if (!isset($isbn[$i])) {
+ return Isbn::TOO_SHORT_ERROR;
+ }
+
+ if ('X' === $isbn[$i]) {
+ $digit = 10;
+ } elseif (ctype_digit($isbn[$i])) {
+ $digit = $isbn[$i];
+ } else {
+ return Isbn::INVALID_CHARACTERS_ERROR;
+ }
+
+ $checkSum += $digit * (10 - $i);
+ }
+
+ if (isset($isbn[$i])) {
+ return Isbn::TOO_LONG_ERROR;
+ }
+
+ return 0 === $checkSum % 11 ? true : Isbn::CHECKSUM_FAILED_ERROR;
+ }
+
+ /**
+ * @return string|bool
+ */
+ protected function validateIsbn13(string $isbn)
+ {
+ // Error priority:
+ // 1. ERROR_INVALID_CHARACTERS
+ // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
+ // 3. ERROR_CHECKSUM_FAILED
+
+ if (!ctype_digit($isbn)) {
+ return Isbn::INVALID_CHARACTERS_ERROR;
+ }
+
+ $length = \strlen($isbn);
+
+ if ($length < 13) {
+ return Isbn::TOO_SHORT_ERROR;
+ }
+
+ if ($length > 13) {
+ return Isbn::TOO_LONG_ERROR;
+ }
+
+ $checkSum = 0;
+
+ for ($i = 0; $i < 13; $i += 2) {
+ $checkSum += $isbn[$i];
+ }
+
+ for ($i = 1; $i < 12; $i += 2) {
+ $checkSum += $isbn[$i] * 3;
+ }
+
+ return 0 === $checkSum % 10 ? true : Isbn::CHECKSUM_FAILED_ERROR;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getMessage(Isbn $constraint, ?string $type = null)
+ {
+ if (null !== $constraint->message) {
+ return $constraint->message;
+ } elseif (Isbn::ISBN_10 === $type) {
+ return $constraint->isbn10Message;
+ } elseif (Isbn::ISBN_13 === $type) {
+ return $constraint->isbn13Message;
+ }
+
+ return $constraint->bothIsbnMessage;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Isin.php b/lib/symfony/validator/Constraints/Isin.php
new file mode 100644
index 0000000000..19f964222e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Isin.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Laurent Masforné
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Isin extends Constraint
+{
+ public const VALIDATION_LENGTH = 12;
+ public const VALIDATION_PATTERN = '/[A-Z]{2}[A-Z0-9]{9}[0-9]{1}/';
+
+ public const INVALID_LENGTH_ERROR = '88738dfc-9ed5-ba1e-aebe-402a2a9bf58e';
+ public const INVALID_PATTERN_ERROR = '3d08ce0-ded9-a93d-9216-17ac21265b65e';
+ public const INVALID_CHECKSUM_ERROR = '32089b-0ee1-93ba-399e-aa232e62f2d29d';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR',
+ self::INVALID_PATTERN_ERROR => 'INVALID_PATTERN_ERROR',
+ self::INVALID_CHECKSUM_ERROR => 'INVALID_CHECKSUM_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid International Securities Identification Number (ISIN).';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IsinValidator.php b/lib/symfony/validator/Constraints/IsinValidator.php
new file mode 100644
index 0000000000..21539cbcb7
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IsinValidator.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Laurent Masforné
+ *
+ * @see https://en.wikipedia.org/wiki/International_Securities_Identification_Number
+ */
+class IsinValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Isin) {
+ throw new UnexpectedTypeException($constraint, Isin::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = strtoupper($value);
+
+ if (Isin::VALIDATION_LENGTH !== \strlen($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Isin::INVALID_LENGTH_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (!preg_match(Isin::VALIDATION_PATTERN, $value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Isin::INVALID_PATTERN_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (!$this->isCorrectChecksum($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Isin::INVALID_CHECKSUM_ERROR)
+ ->addViolation();
+ }
+ }
+
+ private function isCorrectChecksum(string $input): bool
+ {
+ $characters = str_split($input);
+ foreach ($characters as $i => $char) {
+ $characters[$i] = \intval($char, 36);
+ }
+ $number = implode('', $characters);
+
+ return 0 === $this->context->getValidator()->validate($number, new Luhn())->count();
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Issn.php b/lib/symfony/validator/Constraints/Issn.php
new file mode 100644
index 0000000000..e2c45cb510
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Issn.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Antonio J. García Lagar
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Issn extends Constraint
+{
+ public const TOO_SHORT_ERROR = '6a20dd3d-f463-4460-8e7b-18a1b98abbfb';
+ public const TOO_LONG_ERROR = '37cef893-5871-464e-8b12-7fb79324833c';
+ public const MISSING_HYPHEN_ERROR = '2983286f-8134-4693-957a-1ec4ef887b15';
+ public const INVALID_CHARACTERS_ERROR = 'a663d266-37c2-4ece-a914-ae891940c588';
+ public const INVALID_CASE_ERROR = '7b6dd393-7523-4a6c-b84d-72b91bba5e1a';
+ public const CHECKSUM_FAILED_ERROR = 'b0f92dbc-667c-48de-b526-ad9586d43e85';
+
+ protected const ERROR_NAMES = [
+ self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
+ self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
+ self::MISSING_HYPHEN_ERROR => 'MISSING_HYPHEN_ERROR',
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::INVALID_CASE_ERROR => 'INVALID_CASE_ERROR',
+ self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid ISSN.';
+ public $caseSensitive = false;
+ public $requireHyphen = false;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?bool $caseSensitive = null,
+ ?bool $requireHyphen = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->caseSensitive = $caseSensitive ?? $this->caseSensitive;
+ $this->requireHyphen = $requireHyphen ?? $this->requireHyphen;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/IssnValidator.php b/lib/symfony/validator/Constraints/IssnValidator.php
new file mode 100644
index 0000000000..1962322b49
--- /dev/null
+++ b/lib/symfony/validator/Constraints/IssnValidator.php
@@ -0,0 +1,131 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether the value is a valid ISSN.
+ *
+ * @author Antonio J. García Lagar
+ * @author Bernhard Schussek
+ *
+ * @see https://en.wikipedia.org/wiki/Issn
+ */
+class IssnValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Issn) {
+ throw new UnexpectedTypeException($constraint, Issn::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+ $canonical = $value;
+
+ // 1234-567X
+ // ^
+ if (isset($canonical[4]) && '-' === $canonical[4]) {
+ // remove hyphen
+ $canonical = substr($canonical, 0, 4).substr($canonical, 5);
+ } elseif ($constraint->requireHyphen) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Issn::MISSING_HYPHEN_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $length = \strlen($canonical);
+
+ if ($length < 8) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Issn::TOO_SHORT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if ($length > 8) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Issn::TOO_LONG_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // 1234567X
+ // ^^^^^^^ digits only
+ if (!ctype_digit(substr($canonical, 0, 7))) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Issn::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // 1234567X
+ // ^ digit, x or X
+ if (!ctype_digit($canonical[7]) && 'x' !== $canonical[7] && 'X' !== $canonical[7]) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Issn::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // 1234567X
+ // ^ case-sensitive?
+ if ($constraint->caseSensitive && 'x' === $canonical[7]) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Issn::INVALID_CASE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // Calculate a checksum. "X" equals 10.
+ $checkSum = 'X' === $canonical[7] || 'x' === $canonical[7] ? 10 : $canonical[7];
+
+ for ($i = 0; $i < 7; ++$i) {
+ // Multiply the first digit by 8, the second by 7, etc.
+ $checkSum += (8 - $i) * (int) $canonical[$i];
+ }
+
+ if (0 !== $checkSum % 11) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Issn::CHECKSUM_FAILED_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Json.php b/lib/symfony/validator/Constraints/Json.php
new file mode 100644
index 0000000000..05018e7a00
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Json.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Imad ZAIRIG
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Json extends Constraint
+{
+ public const INVALID_JSON_ERROR = '0789c8ad-2d2b-49a4-8356-e2ce63998504';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_JSON_ERROR => 'INVALID_JSON_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be valid JSON.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/JsonValidator.php b/lib/symfony/validator/Constraints/JsonValidator.php
new file mode 100644
index 0000000000..9134da9dd6
--- /dev/null
+++ b/lib/symfony/validator/Constraints/JsonValidator.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Imad ZAIRIG
+ */
+class JsonValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Json) {
+ throw new UnexpectedTypeException($constraint, Json::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (!json_validate($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Json::INVALID_JSON_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Language.php b/lib/symfony/validator/Constraints/Language.php
new file mode 100644
index 0000000000..b354144717
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Language.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Languages;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Language extends Constraint
+{
+ public const NO_SUCH_LANGUAGE_ERROR = 'ee65fec4-9a20-4202-9f39-ca558cd7bdf7';
+
+ protected const ERROR_NAMES = [
+ self::NO_SUCH_LANGUAGE_ERROR => 'NO_SUCH_LANGUAGE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid language.';
+ public $alpha3 = false;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?bool $alpha3 = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ if (!class_exists(Languages::class)) {
+ throw new LogicException('The Intl component is required to use the Language constraint. Try running "composer require symfony/intl".');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->alpha3 = $alpha3 ?? $this->alpha3;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/LanguageValidator.php b/lib/symfony/validator/Constraints/LanguageValidator.php
new file mode 100644
index 0000000000..4706c33569
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LanguageValidator.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Languages;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether a value is a valid language code.
+ *
+ * @author Bernhard Schussek
+ */
+class LanguageValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Language) {
+ throw new UnexpectedTypeException($constraint, Language::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if ($constraint->alpha3 ? !Languages::alpha3CodeExists($value) : !Languages::exists($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Language::NO_SUCH_LANGUAGE_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Length.php b/lib/symfony/validator/Constraints/Length.php
new file mode 100644
index 0000000000..93e797d302
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Length.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Length extends Constraint
+{
+ public const TOO_SHORT_ERROR = '9ff3fdc4-b214-49db-8718-39c315e33d45';
+ public const TOO_LONG_ERROR = 'd94b19cc-114f-4f44-9cc4-4138e80a87b9';
+ public const NOT_EQUAL_LENGTH_ERROR = '4b6f5c76-22b4-409d-af16-fbe823ba9332';
+ public const INVALID_CHARACTERS_ERROR = '35e6a710-aa2e-4719-b58e-24b35749b767';
+
+ protected const ERROR_NAMES = [
+ self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
+ self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
+ self::NOT_EQUAL_LENGTH_ERROR => 'NOT_EQUAL_LENGTH_ERROR',
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ ];
+
+ public const COUNT_BYTES = 'bytes';
+ public const COUNT_CODEPOINTS = 'codepoints';
+ public const COUNT_GRAPHEMES = 'graphemes';
+
+ private const VALID_COUNT_UNITS = [
+ self::COUNT_BYTES,
+ self::COUNT_CODEPOINTS,
+ self::COUNT_GRAPHEMES,
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $maxMessage = 'This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.';
+ public $minMessage = 'This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.';
+ public $exactMessage = 'This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.';
+ public $charsetMessage = 'This value does not match the expected {{ charset }} charset.';
+ public $max;
+ public $min;
+ public $charset = 'UTF-8';
+ /** @var callable|null */
+ public $normalizer;
+ /** @var self::COUNT_* */
+ public string $countUnit = self::COUNT_CODEPOINTS;
+
+ /**
+ * @param self::COUNT_*|null $countUnit
+ */
+ public function __construct(
+ int|array|null $exactly = null,
+ ?int $min = null,
+ ?int $max = null,
+ ?string $charset = null,
+ ?callable $normalizer = null,
+ ?string $countUnit = null,
+ ?string $exactMessage = null,
+ ?string $minMessage = null,
+ ?string $maxMessage = null,
+ ?string $charsetMessage = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array $options = [],
+ ) {
+ if (\is_array($exactly)) {
+ $options = array_merge($exactly, $options);
+ $exactly = $options['value'] ?? null;
+ }
+
+ $min ??= $options['min'] ?? null;
+ $max ??= $options['max'] ?? null;
+
+ unset($options['value'], $options['min'], $options['max']);
+
+ if (null !== $exactly && null === $min && null === $max) {
+ $min = $max = $exactly;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->min = $min;
+ $this->max = $max;
+ $this->charset = $charset ?? $this->charset;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+ $this->countUnit = $countUnit ?? $this->countUnit;
+ $this->exactMessage = $exactMessage ?? $this->exactMessage;
+ $this->minMessage = $minMessage ?? $this->minMessage;
+ $this->maxMessage = $maxMessage ?? $this->maxMessage;
+ $this->charsetMessage = $charsetMessage ?? $this->charsetMessage;
+
+ if (null === $this->min && null === $this->max) {
+ throw new MissingOptionsException(\sprintf('Either option "min" or "max" must be given for constraint "%s".', __CLASS__), ['min', 'max']);
+ }
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+
+ if (!\in_array($this->countUnit, self::VALID_COUNT_UNITS)) {
+ throw new InvalidArgumentException(\sprintf('The "countUnit" option must be one of the "%s"::COUNT_* constants ("%s" given).', __CLASS__, $this->countUnit));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/LengthValidator.php b/lib/symfony/validator/Constraints/LengthValidator.php
new file mode 100644
index 0000000000..c92fca0d59
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LengthValidator.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class LengthValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Length) {
+ throw new UnexpectedTypeException($constraint, Length::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $stringValue = (string) $value;
+
+ if (null !== $constraint->normalizer) {
+ $stringValue = ($constraint->normalizer)($stringValue);
+ }
+
+ try {
+ $invalidCharset = !@mb_check_encoding($stringValue, $constraint->charset);
+ } catch (\ValueError $e) {
+ if (!str_starts_with($e->getMessage(), 'mb_check_encoding(): Argument #2 ($encoding) must be a valid encoding')) {
+ throw $e;
+ }
+
+ $invalidCharset = true;
+ }
+
+ $length = $invalidCharset ? 0 : match ($constraint->countUnit) {
+ Length::COUNT_BYTES => \strlen($stringValue),
+ Length::COUNT_CODEPOINTS => mb_strlen($stringValue, $constraint->charset),
+ Length::COUNT_GRAPHEMES => grapheme_strlen($stringValue),
+ };
+
+ if ($invalidCharset || false === ($length ?? false)) {
+ $this->context->buildViolation($constraint->charsetMessage)
+ ->setParameter('{{ value }}', $this->formatValue($stringValue))
+ ->setParameter('{{ charset }}', $constraint->charset)
+ ->setInvalidValue($value)
+ ->setCode(Length::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (null !== $constraint->max && $length > $constraint->max) {
+ $exactlyOptionEnabled = $constraint->min == $constraint->max;
+
+ $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage)
+ ->setParameter('{{ value }}', $this->formatValue($stringValue))
+ ->setParameter('{{ limit }}', $constraint->max)
+ ->setParameter('{{ value_length }}', $length)
+ ->setInvalidValue($value)
+ ->setPlural((int) $constraint->max)
+ ->setCode($exactlyOptionEnabled ? Length::NOT_EQUAL_LENGTH_ERROR : Length::TOO_LONG_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (null !== $constraint->min && $length < $constraint->min) {
+ $exactlyOptionEnabled = $constraint->min == $constraint->max;
+
+ $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage)
+ ->setParameter('{{ value }}', $this->formatValue($stringValue))
+ ->setParameter('{{ limit }}', $constraint->min)
+ ->setParameter('{{ value_length }}', $length)
+ ->setInvalidValue($value)
+ ->setPlural((int) $constraint->min)
+ ->setCode($exactlyOptionEnabled ? Length::NOT_EQUAL_LENGTH_ERROR : Length::TOO_SHORT_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/LessThan.php b/lib/symfony/validator/Constraints/LessThan.php
new file mode 100644
index 0000000000..cf4144d6d2
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LessThan.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class LessThan extends AbstractComparison
+{
+ public const TOO_HIGH_ERROR = '079d7420-2d13-460c-8756-de810eeb37d2';
+
+ protected const ERROR_NAMES = [
+ self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be less than {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/LessThanOrEqual.php b/lib/symfony/validator/Constraints/LessThanOrEqual.php
new file mode 100644
index 0000000000..84e31abfc0
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LessThanOrEqual.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class LessThanOrEqual extends AbstractComparison
+{
+ public const TOO_HIGH_ERROR = '30fbb013-d015-4232-8b3b-8f3be97a7e14';
+
+ protected const ERROR_NAMES = [
+ self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be less than or equal to {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/LessThanOrEqualValidator.php b/lib/symfony/validator/Constraints/LessThanOrEqualValidator.php
new file mode 100644
index 0000000000..55b3b0a12b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LessThanOrEqualValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values are less than or equal to the previous (<=).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class LessThanOrEqualValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return null === $value2 || $value1 <= $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return LessThanOrEqual::TOO_HIGH_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/LessThanValidator.php b/lib/symfony/validator/Constraints/LessThanValidator.php
new file mode 100644
index 0000000000..b6cbae18bd
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LessThanValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values are less than the previous (<).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class LessThanValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return null === $value2 || $value1 < $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return LessThan::TOO_HIGH_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Locale.php b/lib/symfony/validator/Constraints/Locale.php
new file mode 100644
index 0000000000..9470f1ac8e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Locale.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Locales;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Locale extends Constraint
+{
+ public const NO_SUCH_LOCALE_ERROR = 'a0af4293-1f1a-4a1c-a328-979cba6182a2';
+
+ protected const ERROR_NAMES = [
+ self::NO_SUCH_LOCALE_ERROR => 'NO_SUCH_LOCALE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid locale.';
+ public $canonicalize = true;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?bool $canonicalize = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ if (!class_exists(Locales::class)) {
+ throw new LogicException('The Intl component is required to use the Locale constraint. Try running "composer require symfony/intl".');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->canonicalize = $canonicalize ?? $this->canonicalize;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/LocaleValidator.php b/lib/symfony/validator/Constraints/LocaleValidator.php
new file mode 100644
index 0000000000..11045ca95f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LocaleValidator.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Locales;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether a value is a valid locale code.
+ *
+ * @author Bernhard Schussek
+ */
+class LocaleValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Locale) {
+ throw new UnexpectedTypeException($constraint, Locale::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $inputValue = (string) $value;
+ $value = $inputValue;
+ if ($constraint->canonicalize) {
+ $value = \Locale::canonicalize($value);
+ }
+
+ if (null === $value || !Locales::exists($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($inputValue))
+ ->setCode(Locale::NO_SUCH_LOCALE_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Luhn.php b/lib/symfony/validator/Constraints/Luhn.php
new file mode 100644
index 0000000000..3725c3bc52
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Luhn.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Metadata for the LuhnValidator.
+ *
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Tim Nagel
+ * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Luhn extends Constraint
+{
+ public const INVALID_CHARACTERS_ERROR = 'dfad6d23-1b74-4374-929b-5cbb56fc0d9e';
+ public const CHECKSUM_FAILED_ERROR = '4d760774-3f50-4cd5-a6d5-b10a3299d8d3';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'Invalid card number.';
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/LuhnValidator.php b/lib/symfony/validator/Constraints/LuhnValidator.php
new file mode 100644
index 0000000000..a3f871e339
--- /dev/null
+++ b/lib/symfony/validator/Constraints/LuhnValidator.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates a PAN using the LUHN Algorithm.
+ *
+ * For a list of example card numbers that are used to test this
+ * class, please see the LuhnValidatorTest class.
+ *
+ * @see http://en.wikipedia.org/wiki/Luhn_algorithm
+ *
+ * @author Tim Nagel
+ * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/
+ * @author Bernhard Schussek
+ */
+class LuhnValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Luhn) {
+ throw new UnexpectedTypeException($constraint, Luhn::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ // Work with strings only, because long numbers are represented as floats
+ // internally and don't work with strlen()
+ if (!\is_string($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (!ctype_digit($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Luhn::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $checkSum = 0;
+ $length = \strlen($value);
+
+ for ($i = $length - 1; $i >= 0; --$i) {
+ if (($i % 2) ^ ($length % 2)) {
+ // Starting with the last digit and walking left, add every second
+ // digit to the check sum
+ // e.g. 7 9 9 2 7 3 9 8 7 1 3
+ // ^ ^ ^ ^ ^ ^
+ // = 7 + 9 + 7 + 9 + 7 + 3
+ $checkSum += (int) $value[$i];
+ } else {
+ // Starting with the second last digit and walking left, double every
+ // second digit and add it to the check sum
+ // For doubles greater than 9, sum the individual digits
+ // e.g. 7 9 9 2 7 3 9 8 7 1 3
+ // ^ ^ ^ ^ ^
+ // = 1+8 + 4 + 6 + 1+6 + 2
+ $checkSum += (((int) (2 * $value[$i] / 10)) + (2 * $value[$i]) % 10);
+ }
+ }
+
+ if (0 === $checkSum || 0 !== $checkSum % 10) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Luhn::CHECKSUM_FAILED_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Negative.php b/lib/symfony/validator/Constraints/Negative.php
new file mode 100644
index 0000000000..c13ebcb4a8
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Negative.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Jan Schädlich
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Negative extends LessThan
+{
+ use ZeroComparisonConstraintTrait;
+
+ public $message = 'This value should be negative.';
+}
diff --git a/lib/symfony/validator/Constraints/NegativeOrZero.php b/lib/symfony/validator/Constraints/NegativeOrZero.php
new file mode 100644
index 0000000000..5be735c312
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NegativeOrZero.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Jan Schädlich
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class NegativeOrZero extends LessThanOrEqual
+{
+ use ZeroComparisonConstraintTrait;
+
+ public $message = 'This value should be either negative or zero.';
+}
diff --git a/lib/symfony/validator/Constraints/NoSuspiciousCharacters.php b/lib/symfony/validator/Constraints/NoSuspiciousCharacters.php
new file mode 100644
index 0000000000..b64d26865b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NoSuspiciousCharacters.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Mathieu Lechat
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class NoSuspiciousCharacters extends Constraint
+{
+ public const RESTRICTION_LEVEL_ERROR = '1ece07dc-dca2-45f1-ba47-8d7dc3a12774';
+ public const INVISIBLE_ERROR = '6ed60e6c-179b-4e93-8a6c-667d85c6de5e';
+ public const MIXED_NUMBERS_ERROR = '9f01fc26-3bc4-44b1-a6b1-c08e2412053a';
+ public const HIDDEN_OVERLAY_ERROR = '56380dc5-0476-4f04-bbaa-b68cd1c2d974';
+
+ protected const ERROR_NAMES = [
+ self::RESTRICTION_LEVEL_ERROR => 'RESTRICTION_LEVEL_ERROR',
+ self::INVISIBLE_ERROR => 'INVISIBLE_ERROR',
+ self::MIXED_NUMBERS_ERROR => 'MIXED_NUMBERS_ERROR',
+ self::HIDDEN_OVERLAY_ERROR => 'INVALID_CASE_ERROR',
+ ];
+
+ /**
+ * Check a string for the presence of invisible characters such as zero-width spaces,
+ * or character sequences that are likely not to display such as multiple occurrences of the same non-spacing mark.
+ */
+ public const CHECK_INVISIBLE = 32;
+
+ /**
+ * Check that a string does not mix numbers from different numbering systems;
+ * for example “8” (Digit Eight) and “৪” (Bengali Digit Four).
+ */
+ public const CHECK_MIXED_NUMBERS = 128;
+
+ /**
+ * Check that a string does not have a combining character following a character in which it would be hidden;
+ * for example “i” (Latin Small Letter I) followed by a U+0307 (Combining Dot Above).
+ */
+ public const CHECK_HIDDEN_OVERLAY = 256;
+
+ /** @see https://unicode.org/reports/tr39/#ascii_only */
+ public const RESTRICTION_LEVEL_ASCII = 268435456;
+
+ /** @see https://unicode.org/reports/tr39/#single_script */
+ public const RESTRICTION_LEVEL_SINGLE_SCRIPT = 536870912;
+
+ /** @see https://unicode.org/reports/tr39/#highly_restrictive */
+ public const RESTRICTION_LEVEL_HIGH = 805306368;
+
+ /** @see https://unicode.org/reports/tr39/#moderately_restrictive */
+ public const RESTRICTION_LEVEL_MODERATE = 1073741824;
+
+ /** @see https://unicode.org/reports/tr39/#minimally_restrictive */
+ public const RESTRICTION_LEVEL_MINIMAL = 1342177280;
+
+ /** @see https://unicode.org/reports/tr39/#unrestricted */
+ public const RESTRICTION_LEVEL_NONE = 1610612736;
+
+ public string $restrictionLevelMessage = 'This value contains characters that are not allowed by the current restriction-level.';
+ public string $invisibleMessage = 'Using invisible characters is not allowed.';
+ public string $mixedNumbersMessage = 'Mixing numbers from different scripts is not allowed.';
+ public string $hiddenOverlayMessage = 'Using hidden overlay characters is not allowed.';
+
+ public int $checks = self::CHECK_INVISIBLE | self::CHECK_MIXED_NUMBERS | self::CHECK_HIDDEN_OVERLAY;
+ public ?int $restrictionLevel = null;
+ public ?array $locales = null;
+
+ /**
+ * @param int-mask-of|null $checks
+ * @param self::RESTRICTION_LEVEL_*|null $restrictionLevel
+ */
+ public function __construct(
+ ?array $options = null,
+ ?string $restrictionLevelMessage = null,
+ ?string $invisibleMessage = null,
+ ?string $mixedNumbersMessage = null,
+ ?string $hiddenOverlayMessage = null,
+ ?int $checks = null,
+ ?int $restrictionLevel = null,
+ ?array $locales = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ if (!class_exists(\Spoofchecker::class)) {
+ throw new LogicException('The intl extension is required to use the NoSuspiciousCharacters constraint.');
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->restrictionLevelMessage = $restrictionLevelMessage ?? $this->restrictionLevelMessage;
+ $this->invisibleMessage = $invisibleMessage ?? $this->invisibleMessage;
+ $this->mixedNumbersMessage = $mixedNumbersMessage ?? $this->mixedNumbersMessage;
+ $this->hiddenOverlayMessage = $hiddenOverlayMessage ?? $this->hiddenOverlayMessage;
+ $this->checks = $checks ?? $this->checks;
+ $this->restrictionLevel = $restrictionLevel ?? $this->restrictionLevel;
+ $this->locales = $locales ?? $this->locales;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NoSuspiciousCharactersValidator.php b/lib/symfony/validator/Constraints/NoSuspiciousCharactersValidator.php
new file mode 100644
index 0000000000..659de93f9e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NoSuspiciousCharactersValidator.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Mathieu Lechat
+ */
+class NoSuspiciousCharactersValidator extends ConstraintValidator
+{
+ private const CHECK_RESTRICTION_LEVEL = 16;
+ private const CHECK_SINGLE_SCRIPT = 16;
+ private const CHECK_CHAR_LIMIT = 64;
+
+ private const CHECK_ERROR = [
+ self::CHECK_RESTRICTION_LEVEL => [
+ 'code' => NoSuspiciousCharacters::RESTRICTION_LEVEL_ERROR,
+ 'messageProperty' => 'restrictionLevelMessage',
+ ],
+ NoSuspiciousCharacters::CHECK_INVISIBLE => [
+ 'code' => NoSuspiciousCharacters::INVISIBLE_ERROR,
+ 'messageProperty' => 'invisibleMessage',
+ ],
+ self::CHECK_CHAR_LIMIT => [
+ 'code' => NoSuspiciousCharacters::RESTRICTION_LEVEL_ERROR,
+ 'messageProperty' => 'restrictionLevelMessage',
+ ],
+ NoSuspiciousCharacters::CHECK_MIXED_NUMBERS => [
+ 'code' => NoSuspiciousCharacters::MIXED_NUMBERS_ERROR,
+ 'messageProperty' => 'mixedNumbersMessage',
+ ],
+ NoSuspiciousCharacters::CHECK_HIDDEN_OVERLAY => [
+ 'code' => NoSuspiciousCharacters::HIDDEN_OVERLAY_ERROR,
+ 'messageProperty' => 'hiddenOverlayMessage',
+ ],
+ ];
+
+ /**
+ * @param string[] $defaultLocales
+ */
+ public function __construct(private readonly array $defaultLocales = [])
+ {
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof NoSuspiciousCharacters) {
+ throw new UnexpectedTypeException($constraint, NoSuspiciousCharacters::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ if ('' === $value = (string) $value) {
+ return;
+ }
+
+ $checker = new \Spoofchecker();
+ $checks = $constraint->checks;
+
+ if (method_exists($checker, 'setRestrictionLevel')) {
+ $checks |= self::CHECK_RESTRICTION_LEVEL;
+ $checker->setRestrictionLevel($constraint->restrictionLevel ?? NoSuspiciousCharacters::RESTRICTION_LEVEL_MODERATE);
+ } elseif (NoSuspiciousCharacters::RESTRICTION_LEVEL_MINIMAL === $constraint->restrictionLevel) {
+ $checks |= self::CHECK_CHAR_LIMIT;
+ } elseif (NoSuspiciousCharacters::RESTRICTION_LEVEL_SINGLE_SCRIPT === $constraint->restrictionLevel) {
+ $checks |= self::CHECK_SINGLE_SCRIPT | self::CHECK_CHAR_LIMIT;
+ } elseif ($constraint->restrictionLevel) {
+ throw new LogicException('You can only use one of RESTRICTION_LEVEL_NONE, RESTRICTION_LEVEL_MINIMAL or RESTRICTION_LEVEL_SINGLE_SCRIPT with intl compiled against ICU < 58.');
+ } else {
+ $checks |= self::CHECK_SINGLE_SCRIPT;
+ }
+
+ $checker->setAllowedLocales(implode(',', $constraint->locales ?? $this->defaultLocales));
+
+ $checker->setChecks($checks);
+
+ if (!$checker->isSuspicious($value)) {
+ return;
+ }
+
+ foreach (self::CHECK_ERROR as $check => $error) {
+ if (!($checks & $check)) {
+ continue;
+ }
+
+ $checker->setChecks($check);
+
+ if (!$checker->isSuspicious($value)) {
+ continue;
+ }
+
+ $this->context->buildViolation($constraint->{$error['messageProperty']})
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode($error['code'])
+ ->addViolation()
+ ;
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotBlank.php b/lib/symfony/validator/Constraints/NotBlank.php
new file mode 100644
index 0000000000..17ada2770f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotBlank.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ * @author Kévin Dunglas
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class NotBlank extends Constraint
+{
+ public const IS_BLANK_ERROR = 'c1051bb4-d103-4f74-8988-acbcafc7fdc3';
+
+ protected const ERROR_NAMES = [
+ self::IS_BLANK_ERROR => 'IS_BLANK_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should not be blank.';
+ public $allowNull = false;
+ /** @var callable|null */
+ public $normalizer;
+
+ public function __construct(?array $options = null, ?string $message = null, ?bool $allowNull = null, ?callable $normalizer = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options ?? [], $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->allowNull = $allowNull ?? $this->allowNull;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotBlankValidator.php b/lib/symfony/validator/Constraints/NotBlankValidator.php
new file mode 100644
index 0000000000..fa6c794c02
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotBlankValidator.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ * @author Kévin Dunglas
+ */
+class NotBlankValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof NotBlank) {
+ throw new UnexpectedTypeException($constraint, NotBlank::class);
+ }
+
+ if ($constraint->allowNull && null === $value) {
+ return;
+ }
+
+ if (\is_string($value) && null !== $constraint->normalizer) {
+ $value = ($constraint->normalizer)($value);
+ }
+
+ if (false === $value || (empty($value) && '0' != $value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(NotBlank::IS_BLANK_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotCompromisedPassword.php b/lib/symfony/validator/Constraints/NotCompromisedPassword.php
new file mode 100644
index 0000000000..f5970a1787
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotCompromisedPassword.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Checks if a password has been leaked in a data breach.
+ *
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Kévin Dunglas
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class NotCompromisedPassword extends Constraint
+{
+ public const COMPROMISED_PASSWORD_ERROR = 'd9bcdbfe-a9d6-4bfa-a8ff-da5fd93e0f6d';
+
+ protected const ERROR_NAMES = [
+ self::COMPROMISED_PASSWORD_ERROR => 'COMPROMISED_PASSWORD_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This password has been leaked in a data breach, it must not be used. Please use another password.';
+ public $threshold = 1;
+ public $skipOnError = false;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?int $threshold = null,
+ ?bool $skipOnError = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->threshold = $threshold ?? $this->threshold;
+ $this->skipOnError = $skipOnError ?? $this->skipOnError;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotCompromisedPasswordValidator.php b/lib/symfony/validator/Constraints/NotCompromisedPasswordValidator.php
new file mode 100644
index 0000000000..47a9942d83
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotCompromisedPasswordValidator.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\HttpClient\HttpClient;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * Checks if a password has been leaked in a data breach using haveibeenpwned.com's API.
+ * Use a k-anonymity model to protect the password being searched for.
+ *
+ * @see https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
+ *
+ * @author Kévin Dunglas
+ */
+class NotCompromisedPasswordValidator extends ConstraintValidator
+{
+ private const DEFAULT_API_ENDPOINT = 'https://api.pwnedpasswords.com/range/%s';
+
+ private HttpClientInterface $httpClient;
+ private string $charset;
+ private bool $enabled;
+ private string $endpoint;
+
+ public function __construct(?HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $enabled = true, ?string $endpoint = null)
+ {
+ if (null === $httpClient && !class_exists(HttpClient::class)) {
+ throw new LogicException(\sprintf('The "%s" class requires the "HttpClient" component. Try running "composer require symfony/http-client".', self::class));
+ }
+
+ $this->httpClient = $httpClient ?? HttpClient::create();
+ $this->charset = $charset;
+ $this->enabled = $enabled;
+ $this->endpoint = $endpoint ?? self::DEFAULT_API_ENDPOINT;
+ }
+
+ /**
+ * @return void
+ *
+ * @throws ExceptionInterface
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof NotCompromisedPassword) {
+ throw new UnexpectedTypeException($constraint, NotCompromisedPassword::class);
+ }
+
+ if (!$this->enabled) {
+ return;
+ }
+
+ if (null !== $value && !\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+ if ('' === $value) {
+ return;
+ }
+
+ if ('UTF-8' !== $this->charset) {
+ $value = mb_convert_encoding($value, 'UTF-8', $this->charset);
+ }
+
+ $hash = strtoupper(sha1($value));
+ $hashPrefix = substr($hash, 0, 5);
+ $url = \sprintf($this->endpoint, $hashPrefix);
+
+ try {
+ $result = $this->httpClient->request('GET', $url, ['headers' => ['Add-Padding' => 'true']])->getContent();
+ } catch (ExceptionInterface $e) {
+ if ($constraint->skipOnError) {
+ return;
+ }
+
+ throw $e;
+ }
+
+ foreach (explode("\r\n", $result) as $line) {
+ if (!str_contains($line, ':')) {
+ continue;
+ }
+
+ [$hashSuffix, $count] = explode(':', $line);
+
+ if ($hashPrefix.$hashSuffix === $hash && $constraint->threshold <= (int) $count) {
+ $this->context->buildViolation($constraint->message)
+ ->setCode(NotCompromisedPassword::COMPROMISED_PASSWORD_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotEqualTo.php b/lib/symfony/validator/Constraints/NotEqualTo.php
new file mode 100644
index 0000000000..9a5c07b21e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotEqualTo.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class NotEqualTo extends AbstractComparison
+{
+ public const IS_EQUAL_ERROR = 'aa2e33da-25c8-4d76-8c6c-812f02ea89dd';
+
+ protected const ERROR_NAMES = [
+ self::IS_EQUAL_ERROR => 'IS_EQUAL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should not be equal to {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/NotEqualToValidator.php b/lib/symfony/validator/Constraints/NotEqualToValidator.php
new file mode 100644
index 0000000000..9b5413a598
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotEqualToValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values are all unequal (!=).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class NotEqualToValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return $value1 != $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return NotEqualTo::IS_EQUAL_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotIdenticalTo.php b/lib/symfony/validator/Constraints/NotIdenticalTo.php
new file mode 100644
index 0000000000..206c106137
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotIdenticalTo.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class NotIdenticalTo extends AbstractComparison
+{
+ public const IS_IDENTICAL_ERROR = '4aaac518-0dda-4129-a6d9-e216b9b454a0';
+
+ protected const ERROR_NAMES = [
+ self::IS_IDENTICAL_ERROR => 'IS_IDENTICAL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should not be identical to {{ compared_value_type }} {{ compared_value }}.';
+}
diff --git a/lib/symfony/validator/Constraints/NotIdenticalToValidator.php b/lib/symfony/validator/Constraints/NotIdenticalToValidator.php
new file mode 100644
index 0000000000..ef7d2f43a5
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotIdenticalToValidator.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Validates values aren't identical (!==).
+ *
+ * @author Daniel Holmes
+ * @author Bernhard Schussek
+ */
+class NotIdenticalToValidator extends AbstractComparisonValidator
+{
+ protected function compareValues(mixed $value1, mixed $value2): bool
+ {
+ return $value1 !== $value2;
+ }
+
+ protected function getErrorCode(): ?string
+ {
+ return NotIdenticalTo::IS_IDENTICAL_ERROR;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotNull.php b/lib/symfony/validator/Constraints/NotNull.php
new file mode 100644
index 0000000000..b8523466c7
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotNull.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class NotNull extends Constraint
+{
+ public const IS_NULL_ERROR = 'ad32d13f-c3d4-423b-909a-857b961eb720';
+
+ protected const ERROR_NAMES = [
+ self::IS_NULL_ERROR => 'IS_NULL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should not be null.';
+
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($options ?? [], $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/NotNullValidator.php b/lib/symfony/validator/Constraints/NotNullValidator.php
new file mode 100644
index 0000000000..3f8f951280
--- /dev/null
+++ b/lib/symfony/validator/Constraints/NotNullValidator.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class NotNullValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof NotNull) {
+ throw new UnexpectedTypeException($constraint, NotNull::class);
+ }
+
+ if (null === $value) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(NotNull::IS_NULL_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Optional.php b/lib/symfony/validator/Constraints/Optional.php
new file mode 100644
index 0000000000..dab8b4371f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Optional.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+class Optional extends Existence
+{
+}
diff --git a/lib/symfony/validator/Constraints/PasswordStrength.php b/lib/symfony/validator/Constraints/PasswordStrength.php
new file mode 100644
index 0000000000..090d50d672
--- /dev/null
+++ b/lib/symfony/validator/Constraints/PasswordStrength.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Florent Morselli
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+final class PasswordStrength extends Constraint
+{
+ public const STRENGTH_VERY_WEAK = 0;
+ public const STRENGTH_WEAK = 1;
+ public const STRENGTH_MEDIUM = 2;
+ public const STRENGTH_STRONG = 3;
+ public const STRENGTH_VERY_STRONG = 4;
+
+ public const PASSWORD_STRENGTH_ERROR = '4234df00-45dd-49a4-b303-a75dbf8b10d8';
+
+ protected const ERROR_NAMES = [
+ self::PASSWORD_STRENGTH_ERROR => 'PASSWORD_STRENGTH_ERROR',
+ ];
+
+ public string $message = 'The password strength is too low. Please use a stronger password.';
+
+ public int $minScore;
+
+ public function __construct(?array $options = null, ?int $minScore = null, ?array $groups = null, mixed $payload = null, ?string $message = null)
+ {
+ $options['minScore'] ??= self::STRENGTH_MEDIUM;
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->minScore = $minScore ?? $this->minScore;
+ $this->message = $message ?? $this->message;
+
+ if ($this->minScore < 1 || 4 < $this->minScore) {
+ throw new ConstraintDefinitionException(\sprintf('The parameter "minScore" of the "%s" constraint must be an integer between 1 and 4.', self::class));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/PasswordStrengthValidator.php b/lib/symfony/validator/Constraints/PasswordStrengthValidator.php
new file mode 100644
index 0000000000..c3d2b7d76a
--- /dev/null
+++ b/lib/symfony/validator/Constraints/PasswordStrengthValidator.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+final class PasswordStrengthValidator extends ConstraintValidator
+{
+ /**
+ * @param (\Closure(string):PasswordStrength::STRENGTH_*)|null $passwordStrengthEstimator
+ */
+ public function __construct(
+ private readonly ?\Closure $passwordStrengthEstimator = null,
+ ) {
+ }
+
+ public function validate(#[\SensitiveParameter] mixed $value, Constraint $constraint): void
+ {
+ if (!$constraint instanceof PasswordStrength) {
+ throw new UnexpectedTypeException($constraint, PasswordStrength::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ if (!\is_string($value)) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+ $passwordStrengthEstimator = $this->passwordStrengthEstimator ?? self::estimateStrength(...);
+ $strength = $passwordStrengthEstimator($value);
+
+ if ($strength < $constraint->minScore) {
+ $this->context->buildViolation($constraint->message)
+ ->setCode(PasswordStrength::PASSWORD_STRENGTH_ERROR)
+ ->addViolation();
+ }
+ }
+
+ /**
+ * Returns the estimated strength of a password.
+ *
+ * The higher the value, the stronger the password.
+ *
+ * @return PasswordStrength::STRENGTH_*
+ */
+ private static function estimateStrength(#[\SensitiveParameter] string $password): int
+ {
+ if (!$length = \strlen($password)) {
+ return PasswordStrength::STRENGTH_VERY_WEAK;
+ }
+ $password = count_chars($password, 1);
+ $chars = \count($password);
+
+ $control = $digit = $upper = $lower = $symbol = $other = 0;
+ foreach ($password as $chr => $count) {
+ match (true) {
+ $chr < 32 || 127 === $chr => $control = 33,
+ 48 <= $chr && $chr <= 57 => $digit = 10,
+ 65 <= $chr && $chr <= 90 => $upper = 26,
+ 97 <= $chr && $chr <= 122 => $lower = 26,
+ 128 <= $chr => $other = 128,
+ default => $symbol = 33,
+ };
+ }
+
+ $pool = $lower + $upper + $digit + $symbol + $control + $other;
+ $entropy = $chars * log($pool, 2) + ($length - $chars) * log($chars, 2);
+
+ return match (true) {
+ $entropy >= 120 => PasswordStrength::STRENGTH_VERY_STRONG,
+ $entropy >= 100 => PasswordStrength::STRENGTH_STRONG,
+ $entropy >= 80 => PasswordStrength::STRENGTH_MEDIUM,
+ $entropy >= 60 => PasswordStrength::STRENGTH_WEAK,
+ default => PasswordStrength::STRENGTH_VERY_WEAK,
+ };
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Positive.php b/lib/symfony/validator/Constraints/Positive.php
new file mode 100644
index 0000000000..951e944c9a
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Positive.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Jan Schädlich
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Positive extends GreaterThan
+{
+ use ZeroComparisonConstraintTrait;
+
+ public $message = 'This value should be positive.';
+}
diff --git a/lib/symfony/validator/Constraints/PositiveOrZero.php b/lib/symfony/validator/Constraints/PositiveOrZero.php
new file mode 100644
index 0000000000..a7669c6107
--- /dev/null
+++ b/lib/symfony/validator/Constraints/PositiveOrZero.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Jan Schädlich
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class PositiveOrZero extends GreaterThanOrEqual
+{
+ use ZeroComparisonConstraintTrait;
+
+ public $message = 'This value should be either positive or zero.';
+}
diff --git a/lib/symfony/validator/Constraints/Range.php b/lib/symfony/validator/Constraints/Range.php
new file mode 100644
index 0000000000..48dc39487f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Range.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Range extends Constraint
+{
+ public const INVALID_CHARACTERS_ERROR = 'ad9a9798-7a99-4df7-8ce9-46e416a1e60b';
+ public const NOT_IN_RANGE_ERROR = '04b91c99-a946-4221-afc5-e65ebac401eb';
+ public const TOO_HIGH_ERROR = '2d28afcb-e32e-45fb-a815-01c431a86a69';
+ public const TOO_LOW_ERROR = '76454e69-502c-46c5-9643-f447d837c4d5';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::NOT_IN_RANGE_ERROR => 'NOT_IN_RANGE_ERROR',
+ self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
+ self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $notInRangeMessage = 'This value should be between {{ min }} and {{ max }}.';
+ public $minMessage = 'This value should be {{ limit }} or more.';
+ public $maxMessage = 'This value should be {{ limit }} or less.';
+ public $invalidMessage = 'This value should be a valid number.';
+ public $invalidDateTimeMessage = 'This value is not a valid datetime.';
+ public $min;
+ public $minPropertyPath;
+ public $max;
+ public $maxPropertyPath;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $notInRangeMessage = null,
+ ?string $minMessage = null,
+ ?string $maxMessage = null,
+ ?string $invalidMessage = null,
+ ?string $invalidDateTimeMessage = null,
+ mixed $min = null,
+ ?string $minPropertyPath = null,
+ mixed $max = null,
+ ?string $maxPropertyPath = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->notInRangeMessage = $notInRangeMessage ?? $this->notInRangeMessage;
+ $this->minMessage = $minMessage ?? $this->minMessage;
+ $this->maxMessage = $maxMessage ?? $this->maxMessage;
+ $this->invalidMessage = $invalidMessage ?? $this->invalidMessage;
+ $this->invalidDateTimeMessage = $invalidDateTimeMessage ?? $this->invalidDateTimeMessage;
+ $this->min = $min ?? $this->min;
+ $this->minPropertyPath = $minPropertyPath ?? $this->minPropertyPath;
+ $this->max = $max ?? $this->max;
+ $this->maxPropertyPath = $maxPropertyPath ?? $this->maxPropertyPath;
+
+ if (null === $this->min && null === $this->minPropertyPath && null === $this->max && null === $this->maxPropertyPath) {
+ throw new MissingOptionsException(\sprintf('Either option "min", "minPropertyPath", "max" or "maxPropertyPath" must be given for constraint "%s".', __CLASS__), ['min', 'minPropertyPath', 'max', 'maxPropertyPath']);
+ }
+
+ if (null !== $this->min && null !== $this->minPropertyPath) {
+ throw new ConstraintDefinitionException(\sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', static::class));
+ }
+
+ if (null !== $this->max && null !== $this->maxPropertyPath) {
+ throw new ConstraintDefinitionException(\sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', static::class));
+ }
+
+ if ((null !== $this->minPropertyPath || null !== $this->maxPropertyPath) && !class_exists(PropertyAccess::class)) {
+ throw new LogicException(\sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option. Try running "composer require symfony/property-access".', static::class));
+ }
+
+ if (null !== $this->min && null !== $this->max && ($minMessage || $maxMessage || isset($options['minMessage']) || isset($options['maxMessage']))) {
+ throw new ConstraintDefinitionException(\sprintf('The "%s" constraint can not use "minMessage" and "maxMessage" when the "min" and "max" options are both set. Use "notInRangeMessage" instead.', static::class));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/RangeValidator.php b/lib/symfony/validator/Constraints/RangeValidator.php
new file mode 100644
index 0000000000..cc1109c364
--- /dev/null
+++ b/lib/symfony/validator/Constraints/RangeValidator.php
@@ -0,0 +1,194 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
+use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class RangeValidator extends ConstraintValidator
+{
+ private ?PropertyAccessorInterface $propertyAccessor;
+
+ public function __construct(?PropertyAccessorInterface $propertyAccessor = null)
+ {
+ $this->propertyAccessor = $propertyAccessor;
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Range) {
+ throw new UnexpectedTypeException($constraint, Range::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ $min = $this->getLimit($constraint->minPropertyPath, $constraint->min, $constraint);
+ $max = $this->getLimit($constraint->maxPropertyPath, $constraint->max, $constraint);
+
+ if (!is_numeric($value) && !$value instanceof \DateTimeInterface) {
+ if ($this->isParsableDatetimeString($min) && $this->isParsableDatetimeString($max)) {
+ $this->context->buildViolation($constraint->invalidDateTimeMessage)
+ ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
+ ->setCode(Range::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+ } else {
+ $this->context->buildViolation($constraint->invalidMessage)
+ ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
+ ->setCode(Range::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+ }
+
+ return;
+ }
+
+ // Convert strings to DateTimes if comparing another DateTime
+ // This allows to compare with any date/time value supported by
+ // the DateTime constructor:
+ // https://php.net/datetime.formats
+ if ($value instanceof \DateTimeInterface) {
+ if (\is_string($min)) {
+ try {
+ $min = new $value($min);
+ } catch (\Exception) {
+ throw new ConstraintDefinitionException(\sprintf('The min value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $min, get_debug_type($value), get_debug_type($constraint)));
+ }
+ }
+
+ if (\is_string($max)) {
+ try {
+ $max = new $value($max);
+ } catch (\Exception) {
+ throw new ConstraintDefinitionException(\sprintf('The max value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $max, get_debug_type($value), get_debug_type($constraint)));
+ }
+ }
+ }
+
+ $hasLowerLimit = null !== $min;
+ $hasUpperLimit = null !== $max;
+
+ if ($hasLowerLimit && $hasUpperLimit && ($value < $min || $value > $max)) {
+ $message = $constraint->notInRangeMessage;
+ $code = Range::NOT_IN_RANGE_ERROR;
+
+ $violationBuilder = $this->context->buildViolation($message)
+ ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
+ ->setParameter('{{ min }}', $this->formatValue($min, self::PRETTY_DATE))
+ ->setParameter('{{ max }}', $this->formatValue($max, self::PRETTY_DATE))
+ ->setCode($code);
+
+ if (null !== $constraint->maxPropertyPath) {
+ $violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
+ }
+
+ if (null !== $constraint->minPropertyPath) {
+ $violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
+ }
+
+ $violationBuilder->addViolation();
+
+ return;
+ }
+
+ if ($hasUpperLimit && $value > $max) {
+ $violationBuilder = $this->context->buildViolation($constraint->maxMessage)
+ ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
+ ->setParameter('{{ limit }}', $this->formatValue($max, self::PRETTY_DATE))
+ ->setCode(Range::TOO_HIGH_ERROR);
+
+ if (null !== $constraint->maxPropertyPath) {
+ $violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
+ }
+
+ if (null !== $constraint->minPropertyPath) {
+ $violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
+ }
+
+ $violationBuilder->addViolation();
+
+ return;
+ }
+
+ if ($hasLowerLimit && $value < $min) {
+ $violationBuilder = $this->context->buildViolation($constraint->minMessage)
+ ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
+ ->setParameter('{{ limit }}', $this->formatValue($min, self::PRETTY_DATE))
+ ->setCode(Range::TOO_LOW_ERROR);
+
+ if (null !== $constraint->maxPropertyPath) {
+ $violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
+ }
+
+ if (null !== $constraint->minPropertyPath) {
+ $violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
+ }
+
+ $violationBuilder->addViolation();
+ }
+ }
+
+ private function getLimit(?string $propertyPath, mixed $default, Constraint $constraint): mixed
+ {
+ if (null === $propertyPath) {
+ return $default;
+ }
+
+ if (null === $object = $this->context->getObject()) {
+ return $default;
+ }
+
+ try {
+ return $this->getPropertyAccessor()->getValue($object, $propertyPath);
+ } catch (NoSuchPropertyException $e) {
+ throw new ConstraintDefinitionException(\sprintf('Invalid property path "%s" provided to "%s" constraint: ', $propertyPath, get_debug_type($constraint)).$e->getMessage(), 0, $e);
+ } catch (UninitializedPropertyException) {
+ return null;
+ }
+ }
+
+ private function getPropertyAccessor(): PropertyAccessorInterface
+ {
+ return $this->propertyAccessor ??= PropertyAccess::createPropertyAccessor();
+ }
+
+ private function isParsableDatetimeString(mixed $boundary): bool
+ {
+ if (null === $boundary) {
+ return true;
+ }
+
+ if (!\is_string($boundary)) {
+ return false;
+ }
+
+ try {
+ new \DateTimeImmutable($boundary);
+ } catch (\Exception) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Regex.php b/lib/symfony/validator/Constraints/Regex.php
new file mode 100644
index 0000000000..006e5c5073
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Regex.php
@@ -0,0 +1,128 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Regex extends Constraint
+{
+ public const REGEX_FAILED_ERROR = 'de1e3db3-5ed4-4941-aae4-59f3667cc3a3';
+
+ protected const ERROR_NAMES = [
+ self::REGEX_FAILED_ERROR => 'REGEX_FAILED_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not valid.';
+ public $pattern;
+ public $htmlPattern;
+ public $match = true;
+ /** @var callable|null */
+ public $normalizer;
+
+ public function __construct(
+ string|array|null $pattern,
+ ?string $message = null,
+ ?string $htmlPattern = null,
+ ?bool $match = null,
+ ?callable $normalizer = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array $options = [],
+ ) {
+ if (\is_array($pattern)) {
+ $options = array_merge($pattern, $options);
+ } elseif (null !== $pattern) {
+ $options['value'] = $pattern;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->htmlPattern = $htmlPattern ?? $this->htmlPattern;
+ $this->match = $match ?? $this->match;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'pattern';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['pattern'];
+ }
+
+ /**
+ * Converts the htmlPattern to a suitable format for HTML5 pattern.
+ * Example: /^[a-z]+$/ would be converted to [a-z]+
+ * However, if options are specified, it cannot be converted.
+ *
+ * @see http://dev.w3.org/html5/spec/single-page.html#the-pattern-attribute
+ */
+ public function getHtmlPattern(): ?string
+ {
+ // If htmlPattern is specified, use it
+ if (null !== $this->htmlPattern) {
+ return empty($this->htmlPattern)
+ ? null
+ : $this->htmlPattern;
+ }
+
+ // Quit if delimiters not at very beginning/end (e.g. when options are passed)
+ if ($this->pattern[0] !== $this->pattern[\strlen($this->pattern) - 1]) {
+ return null;
+ }
+
+ $delimiter = $this->pattern[0];
+
+ // Unescape the delimiter
+ $pattern = str_replace('\\'.$delimiter, $delimiter, substr($this->pattern, 1, -1));
+
+ // If the pattern is inverted, we can wrap it in
+ // ((?!pattern).)*
+ if (!$this->match) {
+ return '((?!'.$pattern.').)*';
+ }
+
+ // If the pattern contains an or statement, wrap the pattern in
+ // .*(pattern).* and quit. Otherwise we'd need to parse the pattern
+ if (str_contains($pattern, '|')) {
+ return '.*('.$pattern.').*';
+ }
+
+ // Trim leading ^, otherwise prepend .*
+ $pattern = '^' === $pattern[0] ? substr($pattern, 1) : '.*'.$pattern;
+
+ // Trim trailing $, otherwise append .*
+ $pattern = '$' === $pattern[\strlen($pattern) - 1] ? substr($pattern, 0, -1) : $pattern.'.*';
+
+ return $pattern;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/RegexValidator.php b/lib/symfony/validator/Constraints/RegexValidator.php
new file mode 100644
index 0000000000..4e9ae9039f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/RegexValidator.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether a value match or not given regexp pattern.
+ *
+ * @author Bernhard Schussek
+ * @author Joseph Bielawski
+ */
+class RegexValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Regex) {
+ throw new UnexpectedTypeException($constraint, Regex::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (null !== $constraint->normalizer) {
+ $value = ($constraint->normalizer)($value);
+ }
+
+ if ($constraint->match xor preg_match($constraint->pattern, $value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setParameter('{{ pattern }}', $constraint->pattern)
+ ->setCode(Regex::REGEX_FAILED_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Required.php b/lib/symfony/validator/Constraints/Required.php
new file mode 100644
index 0000000000..bd77a909f9
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Required.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * @Annotation
+ * @Target({"ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+class Required extends Existence
+{
+}
diff --git a/lib/symfony/validator/Constraints/Sequentially.php b/lib/symfony/validator/Constraints/Sequentially.php
new file mode 100644
index 0000000000..79901f4806
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Sequentially.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+/**
+ * Use this constraint to sequentially validate nested constraints.
+ * Validation for the nested constraints collection will stop at first violation.
+ *
+ * @Annotation
+ * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Maxime Steinhausser
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Sequentially extends Composite
+{
+ public $constraints = [];
+
+ public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null)
+ {
+ parent::__construct($constraints ?? [], $groups, $payload);
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'constraints';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['constraints'];
+ }
+
+ protected function getCompositeOption(): string
+ {
+ return 'constraints';
+ }
+
+ public function getTargets(): string|array
+ {
+ return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/SequentiallyValidator.php b/lib/symfony/validator/Constraints/SequentiallyValidator.php
new file mode 100644
index 0000000000..d076f3cfdd
--- /dev/null
+++ b/lib/symfony/validator/Constraints/SequentiallyValidator.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Maxime Steinhausser
+ */
+class SequentiallyValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Sequentially) {
+ throw new UnexpectedTypeException($constraint, Sequentially::class);
+ }
+
+ $context = $this->context;
+
+ $validator = $context->getValidator()->inContext($context);
+
+ $originalCount = $validator->getViolations()->count();
+
+ foreach ($constraint->constraints as $c) {
+ if ($originalCount !== $validator->validate($value, $c)->getViolations()->count()) {
+ break;
+ }
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Time.php b/lib/symfony/validator/Constraints/Time.php
new file mode 100644
index 0000000000..b3adbfd9e4
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Time.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Time extends Constraint
+{
+ public const INVALID_FORMAT_ERROR = '9d27b2bb-f755-4fbf-b725-39b1edbdebdf';
+ public const INVALID_TIME_ERROR = '8532f9e1-84b2-4d67-8989-0818bc38533b';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
+ self::INVALID_TIME_ERROR => 'INVALID_TIME_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $withSeconds = true;
+ public $message = 'This value is not a valid time.';
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ?bool $withSeconds = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->withSeconds = $withSeconds ?? $this->withSeconds;
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/TimeValidator.php b/lib/symfony/validator/Constraints/TimeValidator.php
new file mode 100644
index 0000000000..ef422cdf5a
--- /dev/null
+++ b/lib/symfony/validator/Constraints/TimeValidator.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class TimeValidator extends ConstraintValidator
+{
+ public const PATTERN = '/^(\d{2}):(\d{2}):(\d{2})$/D';
+ public const PATTERN_WITHOUT_SECONDS = '/^(\d{2}):(\d{2})$/D';
+
+ /**
+ * Checks whether a time is valid.
+ *
+ * @internal
+ */
+ public static function checkTime(int $hour, int $minute, float $second): bool
+ {
+ return $hour >= 0 && $hour < 24 && $minute >= 0 && $minute < 60 && $second >= 0 && $second < 60;
+ }
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Time) {
+ throw new UnexpectedTypeException($constraint, Time::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (!preg_match($constraint->withSeconds ? static::PATTERN : static::PATTERN_WITHOUT_SECONDS, $value, $matches)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Time::INVALID_FORMAT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (!self::checkTime($matches[1], $matches[2], $constraint->withSeconds ? $matches[3] : 0)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Time::INVALID_TIME_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Timezone.php b/lib/symfony/validator/Constraints/Timezone.php
new file mode 100644
index 0000000000..17d740a49e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Timezone.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Javier Spagnoletti
+ * @author Hugo Hamon
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Timezone extends Constraint
+{
+ public const TIMEZONE_IDENTIFIER_ERROR = '5ce113e6-5e64-4ea2-90fe-d2233956db13';
+ public const TIMEZONE_IDENTIFIER_IN_ZONE_ERROR = 'b57767b1-36c0-40ac-a3d7-629420c775b8';
+ public const TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR = 'c4a22222-dc92-4fc0-abb0-d95b268c7d0b';
+ public const TIMEZONE_IDENTIFIER_INTL_ERROR = '45863c26-88dc-41ba-bf53-c73bd1f7e90d';
+
+ public $zone = \DateTimeZone::ALL;
+ public $countryCode;
+ public $intlCompatible = false;
+ public $message = 'This value is not a valid timezone.';
+
+ protected const ERROR_NAMES = [
+ self::TIMEZONE_IDENTIFIER_ERROR => 'TIMEZONE_IDENTIFIER_ERROR',
+ self::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR => 'TIMEZONE_IDENTIFIER_IN_ZONE_ERROR',
+ self::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR => 'TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR',
+ self::TIMEZONE_IDENTIFIER_INTL_ERROR => 'TIMEZONE_IDENTIFIER_INTL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public function __construct(
+ int|array|null $zone = null,
+ ?string $message = null,
+ ?string $countryCode = null,
+ ?bool $intlCompatible = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array $options = [],
+ ) {
+ if (\is_array($zone)) {
+ $options = array_merge($zone, $options);
+ } elseif (null !== $zone) {
+ $options['value'] = $zone;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->countryCode = $countryCode ?? $this->countryCode;
+ $this->intlCompatible = $intlCompatible ?? $this->intlCompatible;
+
+ if (null === $this->countryCode) {
+ if (0 >= $this->zone || \DateTimeZone::ALL_WITH_BC < $this->zone) {
+ throw new ConstraintDefinitionException('The option "zone" must be a valid range of "\DateTimeZone" constants.');
+ }
+ } elseif (\DateTimeZone::PER_COUNTRY !== (\DateTimeZone::PER_COUNTRY & $this->zone)) {
+ throw new ConstraintDefinitionException('The option "countryCode" can only be used when the "zone" option is configured with "\DateTimeZone::PER_COUNTRY".');
+ }
+ if ($this->intlCompatible && !class_exists(\IntlTimeZone::class)) {
+ throw new ConstraintDefinitionException('The option "intlCompatible" can only be used when the PHP intl extension is available.');
+ }
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'zone';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/TimezoneValidator.php b/lib/symfony/validator/Constraints/TimezoneValidator.php
new file mode 100644
index 0000000000..409deec7cc
--- /dev/null
+++ b/lib/symfony/validator/Constraints/TimezoneValidator.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Intl\Exception\MissingResourceException;
+use Symfony\Component\Intl\Timezones;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether a value is a valid timezone identifier.
+ *
+ * @author Javier Spagnoletti
+ * @author Hugo Hamon
+ */
+class TimezoneValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Timezone) {
+ throw new UnexpectedTypeException($constraint, Timezone::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if ($constraint->intlCompatible && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($value)->getID()) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Timezone::TIMEZONE_IDENTIFIER_INTL_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (
+ \in_array($value, self::getPhpTimezones($constraint->zone, $constraint->countryCode), true)
+ || \in_array($value, self::getIntlTimezones($constraint->zone, $constraint->countryCode), true)
+ ) {
+ return;
+ }
+
+ if ($constraint->countryCode) {
+ $code = Timezone::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR;
+ } elseif (\DateTimeZone::ALL !== $constraint->zone) {
+ $code = Timezone::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR;
+ } else {
+ $code = Timezone::TIMEZONE_IDENTIFIER_ERROR;
+ }
+
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode($code)
+ ->addViolation();
+ }
+
+ private static function getPhpTimezones(int $zone, ?string $countryCode = null): array
+ {
+ if (null !== $countryCode) {
+ try {
+ return @\DateTimeZone::listIdentifiers($zone, $countryCode) ?: [];
+ } catch (\ValueError) {
+ return [];
+ }
+ }
+
+ return \DateTimeZone::listIdentifiers($zone);
+ }
+
+ private static function getIntlTimezones(int $zone, ?string $countryCode = null): array
+ {
+ if (!class_exists(Timezones::class)) {
+ return [];
+ }
+
+ if (null !== $countryCode) {
+ try {
+ return Timezones::forCountryCode($countryCode);
+ } catch (MissingResourceException) {
+ return [];
+ }
+ }
+
+ $timezones = Timezones::getIds();
+
+ if (\DateTimeZone::ALL === (\DateTimeZone::ALL & $zone)) {
+ return $timezones;
+ }
+
+ $filtered = [];
+ foreach ((new \ReflectionClass(\DateTimeZone::class))->getConstants() as $const => $flag) {
+ if ($flag !== ($flag & $zone)) {
+ continue;
+ }
+
+ $filtered[] = array_filter($timezones, static fn ($id) => 0 === stripos($id, $const.'/'));
+ }
+
+ return $filtered ? array_merge(...$filtered) : [];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Traverse.php b/lib/symfony/validator/Constraints/Traverse.php
new file mode 100644
index 0000000000..f4754ff0ac
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Traverse.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @Annotation
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class Traverse extends Constraint
+{
+ public $traverse = true;
+
+ public function __construct(bool|array|null $traverse = null)
+ {
+ if (\is_array($traverse) && \array_key_exists('groups', $traverse)) {
+ throw new ConstraintDefinitionException(\sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
+ }
+
+ parent::__construct($traverse);
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'traverse';
+ }
+
+ public function getTargets(): string|array
+ {
+ return self::CLASS_CONSTRAINT;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Type.php b/lib/symfony/validator/Constraints/Type.php
new file mode 100644
index 0000000000..e6f479789f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Type.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Type extends Constraint
+{
+ public const INVALID_TYPE_ERROR = 'ba785a8c-82cb-4283-967c-3cf342181b40';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_TYPE_ERROR => 'INVALID_TYPE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value should be of type {{ type }}.';
+ public $type;
+
+ public function __construct(string|array|null $type, ?string $message = null, ?array $groups = null, mixed $payload = null, array $options = [])
+ {
+ if (\is_array($type) && \is_string(key($type))) {
+ $options = array_merge($type, $options);
+ } elseif (null !== $type) {
+ $options['value'] = $type;
+ }
+
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+
+ public function getDefaultOption(): ?string
+ {
+ return 'type';
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['type'];
+ }
+}
diff --git a/lib/symfony/validator/Constraints/TypeValidator.php b/lib/symfony/validator/Constraints/TypeValidator.php
new file mode 100644
index 0000000000..0b4513ac03
--- /dev/null
+++ b/lib/symfony/validator/Constraints/TypeValidator.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class TypeValidator extends ConstraintValidator
+{
+ private const VALIDATION_FUNCTIONS = [
+ 'bool' => 'is_bool',
+ 'boolean' => 'is_bool',
+ 'int' => 'is_int',
+ 'integer' => 'is_int',
+ 'long' => 'is_int',
+ 'float' => 'is_float',
+ 'double' => 'is_float',
+ 'real' => 'is_float',
+ 'number' => 'is_int || is_float && !is_nan',
+ 'finite-float' => 'is_float && is_finite',
+ 'finite-number' => 'is_int || is_float && is_finite',
+ 'numeric' => 'is_numeric',
+ 'string' => 'is_string',
+ 'scalar' => 'is_scalar',
+ 'array' => 'is_array',
+ 'iterable' => 'is_iterable',
+ 'countable' => 'is_countable',
+ 'callable' => 'is_callable',
+ 'object' => 'is_object',
+ 'resource' => 'is_resource',
+ 'null' => 'is_null',
+ 'alnum' => 'ctype_alnum',
+ 'alpha' => 'ctype_alpha',
+ 'cntrl' => 'ctype_cntrl',
+ 'digit' => 'ctype_digit',
+ 'graph' => 'ctype_graph',
+ 'lower' => 'ctype_lower',
+ 'print' => 'ctype_print',
+ 'punct' => 'ctype_punct',
+ 'space' => 'ctype_space',
+ 'upper' => 'ctype_upper',
+ 'xdigit' => 'ctype_xdigit',
+ ];
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Type) {
+ throw new UnexpectedTypeException($constraint, Type::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ $types = (array) $constraint->type;
+
+ foreach ($types as $type) {
+ $type = strtolower($type);
+ if (isset(self::VALIDATION_FUNCTIONS[$type]) && match ($type) {
+ 'finite-float' => \is_float($value) && is_finite($value),
+ 'finite-number' => \is_int($value) || \is_float($value) && is_finite($value),
+ 'number' => \is_int($value) || \is_float($value) && !is_nan($value),
+ default => self::VALIDATION_FUNCTIONS[$type]($value),
+ }) {
+ return;
+ }
+
+ if ($value instanceof $type) {
+ return;
+ }
+ }
+
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setParameter('{{ type }}', implode('|', $types))
+ ->setCode(Type::INVALID_TYPE_ERROR)
+ ->addViolation();
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Ulid.php b/lib/symfony/validator/Constraints/Ulid.php
new file mode 100644
index 0000000000..5b2b825a74
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Ulid.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ *
+ * @author Laurent Clouet
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Ulid extends Constraint
+{
+ public const TOO_SHORT_ERROR = '7b44804e-37d5-4df4-9bdd-b738d4a45bb4';
+ public const TOO_LONG_ERROR = '9608249f-6da1-4d53-889e-9864b58c4d37';
+ public const INVALID_CHARACTERS_ERROR = 'e4155739-5135-4258-9c81-ae7b44b5311e';
+ public const TOO_LARGE_ERROR = 'df8cfb9a-ce6d-4a69-ae5a-eea7ab6f278b';
+
+ protected const ERROR_NAMES = [
+ self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
+ self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This is not a valid ULID.';
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/UlidValidator.php b/lib/symfony/validator/Constraints/UlidValidator.php
new file mode 100644
index 0000000000..ad47f66d4f
--- /dev/null
+++ b/lib/symfony/validator/Constraints/UlidValidator.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether the value is a valid ULID (Universally Unique Lexicographically Sortable Identifier).
+ * Cf https://github.com/ulid/spec for ULID specifications.
+ *
+ * @author Laurent Clouet
+ */
+class UlidValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Ulid) {
+ throw new UnexpectedTypeException($constraint, Ulid::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (26 !== \strlen($value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(26 > \strlen($value) ? Ulid::TOO_SHORT_ERROR : Ulid::TOO_LONG_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ if (\strlen($value) !== strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Ulid::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'
+ // Cf https://github.com/ulid/spec#overflow-errors-when-parsing-base32-strings
+ if ($value[0] > '7') {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Ulid::TOO_LARGE_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Unique.php b/lib/symfony/validator/Constraints/Unique.php
new file mode 100644
index 0000000000..4f77d3a7fd
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Unique.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Yevgeniy Zholkevskiy
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Unique extends Constraint
+{
+ public const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a';
+
+ public array|string $fields = [];
+
+ protected const ERROR_NAMES = [
+ self::IS_NOT_UNIQUE => 'IS_NOT_UNIQUE',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This collection should contain only unique elements.';
+ /** @var callable|null */
+ public $normalizer;
+
+ /**
+ * @param array|string $fields the combination of fields that must contain unique values or a set of options
+ */
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?callable $normalizer = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ array|string|null $fields = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+ $this->fields = $fields ?? $this->fields;
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/UniqueValidator.php b/lib/symfony/validator/Constraints/UniqueValidator.php
new file mode 100644
index 0000000000..94d6a89658
--- /dev/null
+++ b/lib/symfony/validator/Constraints/UniqueValidator.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Yevgeniy Zholkevskiy
+ */
+class UniqueValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Unique) {
+ throw new UnexpectedTypeException($constraint, Unique::class);
+ }
+
+ $fields = (array) $constraint->fields;
+
+ if (null === $value) {
+ return;
+ }
+
+ if (!\is_array($value) && !$value instanceof \IteratorAggregate) {
+ throw new UnexpectedValueException($value, 'array|IteratorAggregate');
+ }
+
+ $collectionElements = [];
+ $normalizer = $this->getNormalizer($constraint);
+ foreach ($value as $element) {
+ $element = $normalizer($element);
+
+ if ($fields && !$element = $this->reduceElementKeys($fields, $element)) {
+ continue;
+ }
+
+ if (\in_array($element, $collectionElements, true)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($element))
+ ->setCode(Unique::IS_NOT_UNIQUE)
+ ->addViolation();
+
+ return;
+ }
+ $collectionElements[] = $element;
+ }
+ }
+
+ private function getNormalizer(Unique $unique): callable
+ {
+ if (null === $unique->normalizer) {
+ return static fn ($value) => $value;
+ }
+
+ return $unique->normalizer;
+ }
+
+ private function reduceElementKeys(array $fields, array $element): array
+ {
+ $output = [];
+ foreach ($fields as $field) {
+ if (!\is_string($field)) {
+ throw new UnexpectedTypeException($field, 'string');
+ }
+ if (\array_key_exists($field, $element)) {
+ $output[$field] = $element[$field];
+ }
+ }
+
+ return $output;
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Url.php b/lib/symfony/validator/Constraints/Url.php
new file mode 100644
index 0000000000..0986d8a175
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Url.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Url extends Constraint
+{
+ public const INVALID_URL_ERROR = '57c2f299-1154-4870-89bb-ef3b1f5ad229';
+
+ protected const ERROR_NAMES = [
+ self::INVALID_URL_ERROR => 'INVALID_URL_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ public $message = 'This value is not a valid URL.';
+ public $protocols = ['http', 'https'];
+ public $relativeProtocol = false;
+ /** @var callable|null */
+ public $normalizer;
+
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ ?array $protocols = null,
+ ?bool $relativeProtocol = null,
+ ?callable $normalizer = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->protocols = $protocols ?? $this->protocols;
+ $this->relativeProtocol = $relativeProtocol ?? $this->relativeProtocol;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/UrlValidator.php b/lib/symfony/validator/Constraints/UrlValidator.php
new file mode 100644
index 0000000000..55a545e8ba
--- /dev/null
+++ b/lib/symfony/validator/Constraints/UrlValidator.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @author Bernhard Schussek
+ */
+class UrlValidator extends ConstraintValidator
+{
+ public const PATTERN = <<<'REGEX'
+ {^
+ (%s):// # protocol
+ ((?:[\pL\pN\-._~!$&'()*+,;=]|%%[0-9A-Fa-f]{2})++(?::(?:[:\pL\pN\-._~!$&'()*+,;=]|%%[0-9A-Fa-f]{2})*+)?@)? # basic auth
+ (
+ (?:
+ (?:
+ (?:[\pL\pN\pS\pM\-\_]++\.)+
+ (?:
+ (?:xn--[a-z0-9-]++) # punycode in tld
+ |
+ (?:[\pL\pN\pM]++) # no punycode in tld
+ )
+ ) # a multi-level domain name
+ |
+ [a-z0-9\-\_]++ # a single-level domain name
+ )\.?
+ | # or
+ \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address
+ | # or
+ \[
+ (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
+ \] # an IPv6 address
+ )
+ (:[0-9]+)? # a port (optional)
+ (?:/ (?:[\pL\pN\pS\pM\-._~!$&'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path
+ (?:\? (?:[\pL\pN\-._~!$&'()*+,;=:@/?[\]]|%%[0-9A-Fa-f]{2})* )? # a query (optional)
+ (?:\# (?:[\pL\pN\-._~!$&'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional)
+ $}ixuD
+ REGEX;
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Url) {
+ throw new UnexpectedTypeException($constraint, Url::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+ if ('' === $value) {
+ return;
+ }
+
+ if (null !== $constraint->normalizer) {
+ $value = ($constraint->normalizer)($value);
+ }
+
+ $pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN;
+ $pattern = \sprintf($pattern, implode('|', $constraint->protocols));
+
+ if (!preg_match($pattern, $value)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Url::INVALID_URL_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Uuid.php b/lib/symfony/validator/Constraints/Uuid.php
new file mode 100644
index 0000000000..87fb9b3221
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Uuid.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
+
+/**
+ * @Annotation
+ *
+ * @author Colin O'Dell
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Uuid extends Constraint
+{
+ public const TOO_SHORT_ERROR = 'aa314679-dac9-4f54-bf97-b2049df8f2a3';
+ public const TOO_LONG_ERROR = '494897dd-36f8-4d31-8923-71a8d5f3000d';
+ public const INVALID_CHARACTERS_ERROR = '51120b12-a2bc-41bf-aa53-cd73daf330d0';
+ public const INVALID_HYPHEN_PLACEMENT_ERROR = '98469c83-0309-4f5d-bf95-a496dcaa869c';
+ public const INVALID_VERSION_ERROR = '21ba13b4-b185-4882-ac6f-d147355987eb';
+ public const INVALID_TIME_BASED_VERSION_ERROR = '484081ca-6fbd-11ed-ade8-a3bdfd0fcf2f';
+ public const INVALID_VARIANT_ERROR = '164ef693-2b9d-46de-ad7f-836201f0c2db';
+
+ protected const ERROR_NAMES = [
+ self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
+ self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
+ self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
+ self::INVALID_HYPHEN_PLACEMENT_ERROR => 'INVALID_HYPHEN_PLACEMENT_ERROR',
+ self::INVALID_VERSION_ERROR => 'INVALID_VERSION_ERROR',
+ self::INVALID_VARIANT_ERROR => 'INVALID_VARIANT_ERROR',
+ ];
+
+ /**
+ * @deprecated since Symfony 6.1, use const ERROR_NAMES instead
+ */
+ protected static $errorNames = self::ERROR_NAMES;
+
+ // Possible versions defined by RFC 9562/4122
+ public const V1_MAC = 1;
+ public const V2_DCE = 2;
+ public const V3_MD5 = 3;
+ public const V4_RANDOM = 4;
+ public const V5_SHA1 = 5;
+ public const V6_SORTABLE = 6;
+ public const V7_MONOTONIC = 7;
+ public const V8_CUSTOM = 8;
+
+ public const ALL_VERSIONS = [
+ self::V1_MAC,
+ self::V2_DCE,
+ self::V3_MD5,
+ self::V4_RANDOM,
+ self::V5_SHA1,
+ self::V6_SORTABLE,
+ self::V7_MONOTONIC,
+ self::V8_CUSTOM,
+ ];
+
+ public const TIME_BASED_VERSIONS = [
+ self::V1_MAC,
+ self::V6_SORTABLE,
+ self::V7_MONOTONIC,
+ ];
+
+ /**
+ * Message to display when validation fails.
+ *
+ * @var string
+ */
+ public $message = 'This is not a valid UUID.';
+
+ /**
+ * Strict mode only allows UUIDs that meet the formal definition and formatting per RFC 9562/4122.
+ *
+ * Set this to `false` to allow legacy formats with different dash positioning or wrapping characters
+ *
+ * @var bool
+ */
+ public $strict = true;
+
+ /**
+ * Array of allowed versions (see version constants above).
+ *
+ * All UUID versions are allowed by default
+ *
+ * @var int[]
+ */
+ public $versions = self::ALL_VERSIONS;
+
+ /** @var callable|null */
+ public $normalizer;
+
+ /**
+ * @param int[]|int|null $versions
+ */
+ public function __construct(
+ ?array $options = null,
+ ?string $message = null,
+ array|int|null $versions = null,
+ ?bool $strict = null,
+ ?callable $normalizer = null,
+ ?array $groups = null,
+ mixed $payload = null,
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->versions = (array) ($versions ?? $this->versions);
+ $this->strict = $strict ?? $this->strict;
+ $this->normalizer = $normalizer ?? $this->normalizer;
+
+ if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
+ throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/UuidValidator.php b/lib/symfony/validator/Constraints/UuidValidator.php
new file mode 100644
index 0000000000..120f8a426b
--- /dev/null
+++ b/lib/symfony/validator/Constraints/UuidValidator.php
@@ -0,0 +1,260 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates whether the value is a valid UUID (also known as GUID).
+ *
+ * Strict validation will allow a UUID as specified per RFC 9562/4122.
+ * Loose validation will allow any type of UUID.
+ *
+ * @author Colin O'Dell
+ * @author Bernhard Schussek
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc9562
+ * @see https://en.wikipedia.org/wiki/Universally_unique_identifier
+ */
+class UuidValidator extends ConstraintValidator
+{
+ // The strict pattern matches UUIDs like this:
+ // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
+
+ // Roughly speaking:
+ // x = any hexadecimal character
+ // M = any allowed version {1..8}
+ // N = any allowed variant {8, 9, a, b}
+
+ public const STRICT_LENGTH = 36;
+ public const STRICT_FIRST_HYPHEN_POSITION = 8;
+ public const STRICT_LAST_HYPHEN_POSITION = 23;
+ public const STRICT_VERSION_POSITION = 14;
+ public const STRICT_VARIANT_POSITION = 19;
+
+ // The loose pattern validates similar yet non-compliant UUIDs.
+ // Hyphens are completely optional. If present, they should only appear
+ // between every fourth character:
+ // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
+ // xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx
+ // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+ // The value can also be wrapped with characters like []{}:
+ // {xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx}
+
+ // Neither the version nor the variant is validated by this pattern.
+
+ public const LOOSE_MAX_LENGTH = 39;
+ public const LOOSE_FIRST_HYPHEN_POSITION = 4;
+
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Uuid) {
+ throw new UnexpectedTypeException($constraint, Uuid::class);
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException($value, 'string');
+ }
+
+ $value = (string) $value;
+
+ if (null !== $constraint->normalizer) {
+ $value = ($constraint->normalizer)($value);
+ }
+
+ if ($constraint->strict) {
+ $this->validateStrict($value, $constraint);
+
+ return;
+ }
+
+ $this->validateLoose($value, $constraint);
+ }
+
+ private function validateLoose(string $value, Uuid $constraint): void
+ {
+ // Error priority:
+ // 1. ERROR_INVALID_CHARACTERS
+ // 2. ERROR_INVALID_HYPHEN_PLACEMENT
+ // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
+
+ // Trim any wrapping characters like [] or {} used by some legacy systems
+ $trimmed = trim($value, '[]{}');
+
+ // Position of the next expected hyphen
+ $h = self::LOOSE_FIRST_HYPHEN_POSITION;
+
+ // Expected length
+ $l = self::LOOSE_MAX_LENGTH;
+
+ for ($i = 0; $i < $l; ++$i) {
+ // Check length
+ if (!isset($trimmed[$i])) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::TOO_SHORT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // Hyphens must occur every fifth position
+ // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
+ // ^ ^ ^ ^ ^ ^ ^
+ if ('-' === $trimmed[$i]) {
+ if ($i !== $h) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ $h += 5;
+
+ continue;
+ }
+
+ // Missing hyphens are ignored
+ if ($i === $h) {
+ $h += 4;
+ --$l;
+ }
+
+ // Check characters
+ if (!ctype_xdigit($trimmed[$i])) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ // Check length again
+ if (isset($trimmed[$i])) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::TOO_LONG_ERROR)
+ ->addViolation();
+ }
+ }
+
+ private function validateStrict(string $value, Uuid $constraint): void
+ {
+ // Error priority:
+ // 1. ERROR_INVALID_CHARACTERS
+ // 2. ERROR_INVALID_HYPHEN_PLACEMENT
+ // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
+ // 4. ERROR_INVALID_VERSION
+ // 5. ERROR_INVALID_VARIANT
+
+ // Position of the next expected hyphen
+ $h = self::STRICT_FIRST_HYPHEN_POSITION;
+
+ for ($i = 0; $i < self::STRICT_LENGTH; ++$i) {
+ // Check length
+ if (!isset($value[$i])) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::TOO_SHORT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // Check hyphen placement
+ // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ // ^ ^ ^ ^
+ if ('-' === $value[$i]) {
+ if ($i !== $h) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ // ^
+ if ($h < self::STRICT_LAST_HYPHEN_POSITION) {
+ $h += 5;
+ }
+
+ continue;
+ }
+
+ // Check characters
+ if (!ctype_xdigit($value[$i])) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
+ ->addViolation();
+
+ return;
+ }
+
+ // Missing hyphen
+ if ($i === $h) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ }
+
+ // Check length again
+ if (isset($value[$i])) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::TOO_LONG_ERROR)
+ ->addViolation();
+ }
+
+ // Check version
+ if (!\in_array($value[self::STRICT_VERSION_POSITION], $constraint->versions)) {
+ $code = Uuid::TIME_BASED_VERSIONS === $constraint->versions ? Uuid::INVALID_TIME_BASED_VERSION_ERROR : Uuid::INVALID_VERSION_ERROR;
+
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode($code)
+ ->addViolation();
+ }
+
+ // Check variant - first two bits must equal "10"
+ // 0b10xx
+ // & 0b1100 (12)
+ // = 0b1000 (8)
+ if (8 !== (hexdec($value[self::STRICT_VARIANT_POSITION]) & 12)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(Uuid::INVALID_VARIANT_ERROR)
+ ->addViolation();
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/Valid.php b/lib/symfony/validator/Constraints/Valid.php
new file mode 100644
index 0000000000..b58686e201
--- /dev/null
+++ b/lib/symfony/validator/Constraints/Valid.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
+ *
+ * @author Bernhard Schussek
+ */
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Valid extends Constraint
+{
+ public $traverse = true;
+
+ public function __construct(?array $options = null, ?array $groups = null, $payload = null, ?bool $traverse = null)
+ {
+ parent::__construct($options ?? [], $groups, $payload);
+
+ $this->traverse = $traverse ?? $this->traverse;
+ }
+
+ public function __get(string $option): mixed
+ {
+ if ('groups' === $option) {
+ // when this is reached, no groups have been configured
+ return null;
+ }
+
+ return parent::__get($option);
+ }
+
+ /**
+ * @return void
+ */
+ public function addImplicitGroupName(string $group)
+ {
+ if (null !== $this->groups) {
+ parent::addImplicitGroupName($group);
+ }
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ValidValidator.php b/lib/symfony/validator/Constraints/ValidValidator.php
new file mode 100644
index 0000000000..7c960ffee1
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ValidValidator.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * @author Christian Flothmann
+ */
+class ValidValidator extends ConstraintValidator
+{
+ /**
+ * @return void
+ */
+ public function validate(mixed $value, Constraint $constraint)
+ {
+ if (!$constraint instanceof Valid) {
+ throw new UnexpectedTypeException($constraint, Valid::class);
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ $this->context
+ ->getValidator()
+ ->inContext($this->context)
+ ->validate($value, null, $this->context->getGroup());
+ }
+}
diff --git a/lib/symfony/validator/Constraints/When.php b/lib/symfony/validator/Constraints/When.php
new file mode 100644
index 0000000000..3150284275
--- /dev/null
+++ b/lib/symfony/validator/Constraints/When.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\ExpressionLanguage\Expression;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * @Annotation
+ * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class When extends Composite
+{
+ public $expression;
+ public $constraints = [];
+ public $values = [];
+
+ public function __construct(string|Expression|array $expression, array|Constraint|null $constraints = null, ?array $values = null, ?array $groups = null, $payload = null, array $options = [])
+ {
+ if (!class_exists(ExpressionLanguage::class)) {
+ throw new LogicException(\sprintf('The "symfony/expression-language" component is required to use the "%s" constraint. Try running "composer require symfony/expression-language".', __CLASS__));
+ }
+
+ if (\is_array($expression)) {
+ $options = array_merge($expression, $options);
+ } else {
+ $options['expression'] = $expression;
+
+ if (null !== $constraints) {
+ $options['constraints'] = $constraints;
+ }
+ }
+
+ if (isset($options['constraints']) && !\is_array($options['constraints'])) {
+ $options['constraints'] = [$options['constraints']];
+ }
+
+ if (null !== $groups) {
+ $options['groups'] = $groups;
+ }
+
+ if (null !== $payload) {
+ $options['payload'] = $payload;
+ }
+
+ parent::__construct($options);
+
+ $this->values = $values ?? $this->values;
+ }
+
+ public function getRequiredOptions(): array
+ {
+ return ['expression', 'constraints'];
+ }
+
+ public function getTargets(): string|array
+ {
+ return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
+ }
+
+ protected function getCompositeOption(): string
+ {
+ return 'constraints';
+ }
+}
diff --git a/lib/symfony/validator/Constraints/WhenValidator.php b/lib/symfony/validator/Constraints/WhenValidator.php
new file mode 100644
index 0000000000..c02a450e8c
--- /dev/null
+++ b/lib/symfony/validator/Constraints/WhenValidator.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+final class WhenValidator extends ConstraintValidator
+{
+ public function __construct(private ?ExpressionLanguage $expressionLanguage = null)
+ {
+ }
+
+ public function validate(mixed $value, Constraint $constraint): void
+ {
+ if (!$constraint instanceof When) {
+ throw new UnexpectedTypeException($constraint, When::class);
+ }
+
+ $context = $this->context;
+ $variables = $constraint->values;
+ $variables['value'] = $value;
+ $variables['this'] = $context->getObject();
+
+ if ($this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
+ $context->getValidator()->inContext($context)
+ ->validate($value, $constraint->constraints);
+ }
+ }
+
+ private function getExpressionLanguage(): ExpressionLanguage
+ {
+ if (!class_exists(ExpressionLanguage::class)) {
+ throw new LogicException(\sprintf('The "symfony/expression-language" component is required to use the "%s" validator. Try running "composer require symfony/expression-language".', __CLASS__));
+ }
+
+ return $this->expressionLanguage ??= new ExpressionLanguage();
+ }
+}
diff --git a/lib/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php b/lib/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php
new file mode 100644
index 0000000000..78fab1f54e
--- /dev/null
+++ b/lib/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @internal
+ *
+ * @author Jan Schädlich
+ * @author Alexander M. Turek
+ */
+trait ZeroComparisonConstraintTrait
+{
+ public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null)
+ {
+ $options ??= [];
+
+ if (isset($options['propertyPath'])) {
+ throw new ConstraintDefinitionException(\sprintf('The "propertyPath" option of the "%s" constraint cannot be set.', static::class));
+ }
+
+ if (isset($options['value'])) {
+ throw new ConstraintDefinitionException(\sprintf('The "value" option of the "%s" constraint cannot be set.', static::class));
+ }
+
+ parent::__construct(0, null, $message, $groups, $payload, $options);
+ }
+
+ public function validatedBy(): string
+ {
+ return parent::class.'Validator';
+ }
+}
diff --git a/lib/symfony/validator/ContainerConstraintValidatorFactory.php b/lib/symfony/validator/ContainerConstraintValidatorFactory.php
new file mode 100644
index 0000000000..89e2ce14e2
--- /dev/null
+++ b/lib/symfony/validator/ContainerConstraintValidatorFactory.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator;
+
+use Psr\Container\ContainerInterface;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Exception\ValidatorException;
+
+/**
+ * Uses a service container to create constraint validators.
+ *
+ * @author Kris Wallsmith
+ */
+class ContainerConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
+{
+ private ContainerInterface $container;
+ private array $validators;
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ $this->validators = [];
+ }
+
+ /**
+ * @throws ValidatorException When the validator class does not exist
+ * @throws UnexpectedTypeException When the validator is not an instance of ConstraintValidatorInterface
+ */
+ public function getInstance(Constraint $constraint): ConstraintValidatorInterface
+ {
+ $name = $constraint->validatedBy();
+
+ if (!isset($this->validators[$name])) {
+ if ($this->container->has($name)) {
+ $this->validators[$name] = $this->container->get($name);
+ } else {
+ if (!class_exists($name)) {
+ throw new ValidatorException(\sprintf('Constraint validator "%s" does not exist or is not enabled. Check the "validatedBy" method in your constraint class "%s".', $name, get_debug_type($constraint)));
+ }
+
+ $this->validators[$name] = new $name();
+ }
+ }
+
+ if (!$this->validators[$name] instanceof ConstraintValidatorInterface) {
+ throw new UnexpectedTypeException($this->validators[$name], ConstraintValidatorInterface::class);
+ }
+
+ return $this->validators[$name];
+ }
+}
diff --git a/lib/symfony/validator/Context/ExecutionContext.php b/lib/symfony/validator/Context/ExecutionContext.php
new file mode 100644
index 0000000000..8ab1ec4d5c
--- /dev/null
+++ b/lib/symfony/validator/Context/ExecutionContext.php
@@ -0,0 +1,281 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Context;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationList;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
+use Symfony\Component\Validator\Mapping\MemberMetadata;
+use Symfony\Component\Validator\Mapping\MetadataInterface;
+use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
+use Symfony\Component\Validator\Util\PropertyPath;
+use Symfony\Component\Validator\Validator\LazyProperty;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
+use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * The context used and created by {@link ExecutionContextFactory}.
+ *
+ * @author Bernhard Schussek
+ *
+ * @see ExecutionContextInterface
+ *
+ * @internal since version 2.5. Code against ExecutionContextInterface instead.
+ */
+class ExecutionContext implements ExecutionContextInterface
+{
+ private ValidatorInterface $validator;
+
+ /**
+ * The root value of the validated object graph.
+ */
+ private mixed $root;
+
+ private TranslatorInterface $translator;
+ private ?string $translationDomain;
+
+ /**
+ * The violations generated in the current context.
+ */
+ private ConstraintViolationList $violations;
+
+ /**
+ * The currently validated value.
+ */
+ private mixed $value = null;
+
+ /**
+ * The currently validated object.
+ */
+ private ?object $object = null;
+
+ /**
+ * The property path leading to the current value.
+ */
+ private string $propertyPath = '';
+
+ /**
+ * The current validation metadata.
+ */
+ private ?MetadataInterface $metadata = null;
+
+ /**
+ * The currently validated group.
+ */
+ private ?string $group = null;
+
+ /**
+ * The currently validated constraint.
+ */
+ private ?Constraint $constraint = null;
+
+ /**
+ * Stores which objects have been validated in which group.
+ *
+ * @var bool[][]
+ */
+ private array $validatedObjects = [];
+
+ /**
+ * Stores which class constraint has been validated for which object.
+ *
+ * @var bool[]
+ */
+ private array $validatedConstraints = [];
+
+ /**
+ * Stores which objects have been initialized.
+ *
+ * @var bool[]
+ */
+ private array $initializedObjects = [];
+
+ /**
+ * @var \SplObjectStorage