Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1432552
ParamValidator.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
29 KB
Referenced Files
None
Subscribers
None
ParamValidator.php
View Options
<?php
namespace
Wikimedia\ParamValidator
;
use
DomainException
;
use
InvalidArgumentException
;
use
Wikimedia\Assert\Assert
;
use
Wikimedia\Message\DataMessageValue
;
use
Wikimedia\Message\MessageValue
;
use
Wikimedia\Message\ParamType
;
use
Wikimedia\Message\ScalarParam
;
use
Wikimedia\ObjectFactory\ObjectFactory
;
/**
* Service for formatting and validating API parameters
*
* A settings array is simply an array with keys being the relevant PARAM_*
* constants from this class, TypeDef, and its subclasses.
*
* As a general overview of the architecture here:
* - ParamValidator handles some general validation of the parameter,
* then hands off to a TypeDef subclass to validate the specific representation
* based on the parameter's type.
* - TypeDef subclasses handle conversion between the string representation
* submitted by the client and the output PHP data types, validating that the
* strings are valid representations of the intended type as they do so.
* - ValidationException is used to report fatal errors in the validation back
* to the caller, since the return value represents the successful result of
* the validation and might be any type or class.
* - The Callbacks interface allows ParamValidator to reach out and fetch data
* it needs to perform the validation. Currently that includes:
* - Fetching the value of the parameter being validated (largely since a generic
* caller cannot know whether it needs to fetch a string from $_GET/$_POST or
* an array from $_FILES).
* - Reporting of non-fatal warnings back to the caller.
* - Fetching the "high limits" flag when necessary, to avoid the need for loading
* the user unnecessarily.
*
* @since 1.34
* @unstable
*/
class
ParamValidator
{
// region Constants for parameter settings arrays
/** @name Constants for parameter settings arrays
* These constants are keys in the settings array that define how the
* parameters coming in from the request are to be interpreted.
*
* If a constant is associated with a failure code, the failure code
* and data are described. ValidationExceptions are typically thrown, but
* those indicated as "non-fatal" are instead passed to
* Callbacks::recordCondition().
*
* Additional constants may be defined by TypeDef subclasses, or by other
* libraries for controlling things like auto-generated parameter documentation.
* For purposes of namespacing the constants, the values of all constants
* defined by this library begin with 'param-'.
*
* @{
*/
/**
* (mixed) Default value of the parameter. If omitted, null is the default.
*
* TypeDef::validate() will be informed when the default value was used by the presence of
* 'is-default' in $options.
*/
public
const
PARAM_DEFAULT
=
'param-default'
;
/**
* (string|array) Type of the parameter.
* Must be a registered type or an array of enumerated values (in which case the "enum"
* type must be registered). If omitted, the default is the PHP type of the default value
* (see PARAM_DEFAULT).
*/
public
const
PARAM_TYPE
=
'param-type'
;
/**
* (bool) Indicate that the parameter is required.
*
* Failure codes:
* - 'missingparam': The parameter is omitted/empty (and no default was set). No data.
*/
public
const
PARAM_REQUIRED
=
'param-required'
;
/**
* (bool) Indicate that the parameter is multi-valued.
*
* A multi-valued parameter may be submitted in one of several formats. All
* of the following result in a value of `[ 'a', 'b', 'c' ]`.
* - "a|b|c", i.e. pipe-separated.
* - "\x1Fa\x1Fb\x1Fc", i.e. separated by U+001F, with a signalling U+001F at the start.
* - As a string[], e.g. from a query string like "foo[]=a&foo[]=b&foo[]=c".
*
* Each of the multiple values is passed individually to the TypeDef.
* $options will contain a 'values-list' key holding the entire list.
*
* By default duplicates are removed from the resulting parameter list. Use
* PARAM_ALLOW_DUPLICATES to override that behavior.
*
* Failure codes:
* - 'toomanyvalues': More values were supplied than are allowed. See
* PARAM_ISMULTI_LIMIT1, PARAM_ISMULTI_LIMIT2, and constructor option
* 'ismultiLimits'. Data:
* - 'limit': The limit currently in effect.
* - 'lowlimit': The limit when high limits are not allowed.
* - 'highlimit': The limit when high limits are allowed.
* - 'unrecognizedvalues': Non-fatal. Invalid values were passed and
* PARAM_IGNORE_UNRECOGNIZED_VALUES was set. Data:
* - 'values': The unrecognized values.
*/
public
const
PARAM_ISMULTI
=
'param-ismulti'
;
/**
* (int) Maximum number of multi-valued parameter values allowed
*
* @see PARAM_ISMULTI
*/
public
const
PARAM_ISMULTI_LIMIT1
=
'param-ismulti-limit1'
;
/**
* (int) Maximum number of multi-valued parameter values allowed for users
* allowed high limits.
*
* @see PARAM_ISMULTI
*/
public
const
PARAM_ISMULTI_LIMIT2
=
'param-ismulti-limit2'
;
/**
* (bool|string) Whether a magic "all values" value exists for multi-valued
* enumerated types, and if so what that value is.
*
* When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true,
* this allows for an asterisk ('*') to be passed in place of a pipe-separated list of
* every possible value. If a string is set, it will be used in place of the asterisk.
*/
public
const
PARAM_ALL
=
'param-all'
;
/**
* (bool) Allow the same value to be set more than once when PARAM_ISMULTI is true?
*
* If not truthy, the set of values will be passed through
* `array_values( array_unique() )`. The default is falsey.
*/
public
const
PARAM_ALLOW_DUPLICATES
=
'param-allow-duplicates'
;
/**
* (bool) Indicate that the parameter's value should not be logged.
*
* Failure codes: (non-fatal)
* - 'param-sensitive': Always recorded when the parameter is used.
*/
public
const
PARAM_SENSITIVE
=
'param-sensitive'
;
/**
* (bool) Indicate that a deprecated parameter was used.
*
* Failure codes: (non-fatal)
* - 'param-deprecated': Always recorded when the parameter is used.
*/
public
const
PARAM_DEPRECATED
=
'param-deprecated'
;
/**
* (bool) Whether to downgrade "badvalue" errors to non-fatal when validating multi-valued
* parameters.
* @see PARAM_ISMULTI
*/
public
const
PARAM_IGNORE_UNRECOGNIZED_VALUES
=
'param-ignore-unrecognized-values'
;
/** @} */
// endregion -- end of Constants for parameter settings arrays
/**
* @see TypeDef::OPT_ENFORCE_JSON_TYPES
*/
public
const
OPT_ENFORCE_JSON_TYPES
=
TypeDef
::
OPT_ENFORCE_JSON_TYPES
;
/** Magic "all values" value when PARAM_ALL is true. */
public
const
ALL_DEFAULT_STRING
=
'*'
;
/** A list of standard type names and types that may be passed as `$typeDefs` to __construct(). */
public
const
STANDARD_TYPES
=
[
'boolean'
=>
[
'class'
=>
TypeDef\BooleanDef
::
class
],
'checkbox'
=>
[
'class'
=>
TypeDef\PresenceBooleanDef
::
class
],
'integer'
=>
[
'class'
=>
TypeDef\IntegerDef
::
class
],
'limit'
=>
[
'class'
=>
TypeDef\LimitDef
::
class
],
'float'
=>
[
'class'
=>
TypeDef\FloatDef
::
class
],
'double'
=>
[
'class'
=>
TypeDef\FloatDef
::
class
],
'string'
=>
[
'class'
=>
TypeDef\StringDef
::
class
],
'password'
=>
[
'class'
=>
TypeDef\PasswordDef
::
class
],
'NULL'
=>
[
'class'
=>
TypeDef\StringDef
::
class
,
'args'
=>
[
[
TypeDef\StringDef
::
OPT_ALLOW_EMPTY
=>
true
,
]
],
],
'timestamp'
=>
[
'class'
=>
TypeDef\TimestampDef
::
class
],
'upload'
=>
[
'class'
=>
TypeDef\UploadDef
::
class
],
'enum'
=>
[
'class'
=>
TypeDef\EnumDef
::
class
],
'expiry'
=>
[
'class'
=>
TypeDef\ExpiryDef
::
class
],
];
/** @var Callbacks */
private
$callbacks
;
/** @var ObjectFactory */
private
$objectFactory
;
/** @var (TypeDef|array)[] Map parameter type names to TypeDef objects or ObjectFactory specs */
private
$typeDefs
=
[];
/** @var int Default values for PARAM_ISMULTI_LIMIT1 */
private
$ismultiLimit1
;
/** @var int Default values for PARAM_ISMULTI_LIMIT2 */
private
$ismultiLimit2
;
/**
* @param Callbacks $callbacks
* @param ObjectFactory $objectFactory To turn specs into TypeDef objects
* @param array $options Associative array of additional settings
* - 'typeDefs': (array) As for addTypeDefs(). If omitted, self::STANDARD_TYPES will be used.
* Pass an empty array if you want to start with no registered types.
* - 'ismultiLimits': (int[]) Two ints, being the default values for PARAM_ISMULTI_LIMIT1 and
* PARAM_ISMULTI_LIMIT2. If not given, defaults to `[ 50, 500 ]`.
*/
public
function
__construct
(
Callbacks
$callbacks
,
ObjectFactory
$objectFactory
,
array
$options
=
[]
)
{
$this
->
callbacks
=
$callbacks
;
$this
->
objectFactory
=
$objectFactory
;
$this
->
addTypeDefs
(
$options
[
'typeDefs'
]
??
self
::
STANDARD_TYPES
);
$this
->
ismultiLimit1
=
$options
[
'ismultiLimits'
][
0
]
??
50
;
$this
->
ismultiLimit2
=
$options
[
'ismultiLimits'
][
1
]
??
500
;
}
/**
* List known type names
* @return string[]
*/
public
function
knownTypes
()
{
return
array_keys
(
$this
->
typeDefs
);
}
/**
* Register multiple type handlers
*
* @see addTypeDef()
* @param array $typeDefs Associative array mapping `$name` to `$typeDef`.
*/
public
function
addTypeDefs
(
array
$typeDefs
)
{
foreach
(
$typeDefs
as
$name
=>
$def
)
{
$this
->
addTypeDef
(
$name
,
$def
);
}
}
/**
* Register a type handler
*
* To allow code to omit PARAM_TYPE in settings arrays to derive the type
* from PARAM_DEFAULT, it is strongly recommended that the following types be
* registered: "boolean", "integer", "double", "string", "NULL", and "enum".
*
* When using ObjectFactory specs, the following extra arguments are passed:
* - The Callbacks object for this ParamValidator instance.
*
* @param string $name Type name
* @param TypeDef|array $typeDef Type handler or ObjectFactory spec to create one.
*/
public
function
addTypeDef
(
$name
,
$typeDef
)
{
Assert
::
parameterType
(
[
TypeDef
::
class
,
'array'
],
$typeDef
,
'$typeDef'
);
if
(
isset
(
$this
->
typeDefs
[
$name
]
)
)
{
throw
new
InvalidArgumentException
(
"Type '$name' is already registered"
);
}
$this
->
typeDefs
[
$name
]
=
$typeDef
;
}
/**
* Register a type handler, overriding any existing handler
* @see addTypeDef
* @param string $name Type name
* @param TypeDef|array|null $typeDef As for addTypeDef, or null to unregister a type.
*/
public
function
overrideTypeDef
(
$name
,
$typeDef
)
{
Assert
::
parameterType
(
[
TypeDef
::
class
,
'array'
,
'null'
],
$typeDef
,
'$typeDef'
);
if
(
$typeDef
===
null
)
{
unset
(
$this
->
typeDefs
[
$name
]
);
}
else
{
$this
->
typeDefs
[
$name
]
=
$typeDef
;
}
}
/**
* Test if a type is registered
* @param string $name Type name
* @return bool
*/
public
function
hasTypeDef
(
$name
)
{
return
isset
(
$this
->
typeDefs
[
$name
]
);
}
/**
* Get the TypeDef for a type
* @param string|array $type Any array is considered equivalent to the string "enum".
* @return TypeDef|null
*/
public
function
getTypeDef
(
$type
)
{
if
(
is_array
(
$type
)
)
{
$type
=
'enum'
;
}
if
(
!
isset
(
$this
->
typeDefs
[
$type
]
)
)
{
return
null
;
}
$def
=
$this
->
typeDefs
[
$type
];
if
(
!
$def
instanceof
TypeDef
)
{
$def
=
$this
->
objectFactory
->
createObject
(
$def
,
[
'extraArgs'
=>
[
$this
->
callbacks
],
'assertClass'
=>
TypeDef
::
class
,
]
);
$this
->
typeDefs
[
$type
]
=
$def
;
}
return
$def
;
}
/**
* Logic shared by normalizeSettings() and checkSettings()
* @param array|mixed $settings
* @return array
*/
private
function
normalizeSettingsInternal
(
$settings
)
{
// Shorthand
if
(
!
is_array
(
$settings
)
)
{
$settings
=
[
self
::
PARAM_DEFAULT
=>
$settings
,
];
}
// When type is not given, determine it from the type of the PARAM_DEFAULT
if
(
!
isset
(
$settings
[
self
::
PARAM_TYPE
]
)
)
{
$settings
[
self
::
PARAM_TYPE
]
=
gettype
(
$settings
[
self
::
PARAM_DEFAULT
]
??
null
);
}
return
$settings
;
}
/**
* Normalize a parameter settings array
* @param array|mixed $settings Default value or an array of settings
* using PARAM_* constants.
* @return array
*/
public
function
normalizeSettings
(
$settings
)
{
$settings
=
$this
->
normalizeSettingsInternal
(
$settings
);
$typeDef
=
$this
->
getTypeDef
(
$settings
[
self
::
PARAM_TYPE
]
);
if
(
$typeDef
)
{
$settings
=
$typeDef
->
normalizeSettings
(
$settings
);
}
return
$settings
;
}
/**
* Validate a parameter settings array
*
* This is intended for validation of parameter settings during unit or
* integration testing, and should implement strict checks.
*
* The rest of the code should generally be more permissive.
*
* @param string $name Parameter name
* @param array|mixed $settings Default value or an array of settings
* using PARAM_* constants.
* @param array $options Options array, passed through to the TypeDef and Callbacks.
* @return array
* - 'issues': (string[]) Errors detected in $settings, as English text. If the settings
* are valid, this will be the empty array.
* - 'allowedKeys': (string[]) ParamValidator keys that are allowed in `$settings`.
* - 'messages': (MessageValue[]) Messages to be checked for existence.
*/
public
function
checkSettings
(
string
$name
,
$settings
,
array
$options
):
array
{
$settings
=
$this
->
normalizeSettingsInternal
(
$settings
);
$issues
=
[];
$allowedKeys
=
[
self
::
PARAM_TYPE
,
self
::
PARAM_DEFAULT
,
self
::
PARAM_REQUIRED
,
self
::
PARAM_ISMULTI
,
self
::
PARAM_SENSITIVE
,
self
::
PARAM_DEPRECATED
,
self
::
PARAM_IGNORE_UNRECOGNIZED_VALUES
,
];
$messages
=
[];
$type
=
$settings
[
self
::
PARAM_TYPE
];
$typeDef
=
null
;
if
(
!
is_string
(
$type
)
&&
!
is_array
(
$type
)
)
{
$issues
[
self
::
PARAM_TYPE
]
=
'PARAM_TYPE must be a string or array, got '
.
gettype
(
$type
);
}
else
{
$typeDef
=
$this
->
getTypeDef
(
$settings
[
self
::
PARAM_TYPE
]
);
if
(
!
$typeDef
)
{
if
(
is_array
(
$type
)
)
{
$type
=
'enum'
;
}
$issues
[
self
::
PARAM_TYPE
]
=
"Unknown/unregistered PARAM_TYPE
\"
$type
\"
"
;
}
}
if
(
isset
(
$settings
[
self
::
PARAM_DEFAULT
]
)
)
{
try
{
$this
->
validateValue
(
$name
,
$settings
[
self
::
PARAM_DEFAULT
],
$settings
,
[
'is-default'
=>
true
]
+
$options
);
}
catch
(
ValidationException
$ex
)
{
$issues
[
self
::
PARAM_DEFAULT
]
=
'Value for PARAM_DEFAULT does not validate (code '
.
$ex
->
getFailureMessage
()->
getCode
()
.
')'
;
}
}
if
(
!
is_bool
(
$settings
[
self
::
PARAM_REQUIRED
]
??
false
)
)
{
$issues
[
self
::
PARAM_REQUIRED
]
=
'PARAM_REQUIRED must be boolean, got '
.
gettype
(
$settings
[
self
::
PARAM_REQUIRED
]
);
}
if
(
!
is_bool
(
$settings
[
self
::
PARAM_ISMULTI
]
??
false
)
)
{
$issues
[
self
::
PARAM_ISMULTI
]
=
'PARAM_ISMULTI must be boolean, got '
.
gettype
(
$settings
[
self
::
PARAM_ISMULTI
]
);
}
if
(
!
empty
(
$settings
[
self
::
PARAM_ISMULTI
]
)
)
{
$allowedKeys
=
array_merge
(
$allowedKeys
,
[
self
::
PARAM_ISMULTI_LIMIT1
,
self
::
PARAM_ISMULTI_LIMIT2
,
self
::
PARAM_ALL
,
self
::
PARAM_ALLOW_DUPLICATES
]
);
$limit1
=
$settings
[
self
::
PARAM_ISMULTI_LIMIT1
]
??
$this
->
ismultiLimit1
;
$limit2
=
$settings
[
self
::
PARAM_ISMULTI_LIMIT2
]
??
$this
->
ismultiLimit2
;
if
(
!
is_int
(
$limit1
)
)
{
$issues
[
self
::
PARAM_ISMULTI_LIMIT1
]
=
'PARAM_ISMULTI_LIMIT1 must be an integer, got '
.
gettype
(
$settings
[
self
::
PARAM_ISMULTI_LIMIT1
]
);
}
elseif
(
$limit1
<=
0
)
{
$issues
[
self
::
PARAM_ISMULTI_LIMIT1
]
=
"PARAM_ISMULTI_LIMIT1 must be greater than 0, got $limit1"
;
}
if
(
!
is_int
(
$limit2
)
)
{
$issues
[
self
::
PARAM_ISMULTI_LIMIT2
]
=
'PARAM_ISMULTI_LIMIT2 must be an integer, got '
.
gettype
(
$settings
[
self
::
PARAM_ISMULTI_LIMIT2
]
);
}
elseif
(
$limit2
<
$limit1
)
{
$issues
[
self
::
PARAM_ISMULTI_LIMIT2
]
=
'PARAM_ISMULTI_LIMIT2 must be greater than or equal to PARAM_ISMULTI_LIMIT1, but '
.
"$limit2 < $limit1"
;
}
$all
=
$settings
[
self
::
PARAM_ALL
]
??
false
;
if
(
!
is_string
(
$all
)
&&
!
is_bool
(
$all
)
)
{
$issues
[
self
::
PARAM_ALL
]
=
'PARAM_ALL must be a string or boolean, got '
.
gettype
(
$all
);
}
elseif
(
$all
!==
false
&&
$typeDef
)
{
if
(
$all
===
true
)
{
$all
=
self
::
ALL_DEFAULT_STRING
;
}
$values
=
$typeDef
->
getEnumValues
(
$name
,
$settings
,
$options
);
if
(
!
is_array
(
$values
)
)
{
$issues
[
self
::
PARAM_ALL
]
=
'PARAM_ALL cannot be used with non-enumerated types'
;
}
elseif
(
in_array
(
$all
,
$values
,
true
)
)
{
$issues
[
self
::
PARAM_ALL
]
=
'Value for PARAM_ALL conflicts with an enumerated value'
;
}
}
if
(
!
is_bool
(
$settings
[
self
::
PARAM_ALLOW_DUPLICATES
]
??
false
)
)
{
$issues
[
self
::
PARAM_ALLOW_DUPLICATES
]
=
'PARAM_ALLOW_DUPLICATES must be boolean, got '
.
gettype
(
$settings
[
self
::
PARAM_ALLOW_DUPLICATES
]
);
}
}
if
(
!
is_bool
(
$settings
[
self
::
PARAM_SENSITIVE
]
??
false
)
)
{
$issues
[
self
::
PARAM_SENSITIVE
]
=
'PARAM_SENSITIVE must be boolean, got '
.
gettype
(
$settings
[
self
::
PARAM_SENSITIVE
]
);
}
if
(
!
is_bool
(
$settings
[
self
::
PARAM_DEPRECATED
]
??
false
)
)
{
$issues
[
self
::
PARAM_DEPRECATED
]
=
'PARAM_DEPRECATED must be boolean, got '
.
gettype
(
$settings
[
self
::
PARAM_DEPRECATED
]
);
}
if
(
!
is_bool
(
$settings
[
self
::
PARAM_IGNORE_UNRECOGNIZED_VALUES
]
??
false
)
)
{
$issues
[
self
::
PARAM_IGNORE_UNRECOGNIZED_VALUES
]
=
'PARAM_IGNORE_UNRECOGNIZED_VALUES must be '
.
'boolean, got '
.
gettype
(
$settings
[
self
::
PARAM_IGNORE_UNRECOGNIZED_VALUES
]
);
}
$ret
=
[
'issues'
=>
$issues
,
'allowedKeys'
=>
$allowedKeys
,
'messages'
=>
$messages
];
if
(
$typeDef
)
{
$ret
=
$typeDef
->
checkSettings
(
$name
,
$settings
,
$options
,
$ret
);
}
return
$ret
;
}
/**
* Fetch and validate a parameter value using a settings array
*
* @param string $name Parameter name
* @param array|mixed $settings Default value or an array of settings
* using PARAM_* constants.
* @param array $options Options array, passed through to the TypeDef and Callbacks.
* - An additional option, 'is-default', will be set when the value comes from PARAM_DEFAULT.
* @return mixed Validated parameter value
* @throws ValidationException if the value is invalid
*/
public
function
getValue
(
$name
,
$settings
,
array
$options
=
[]
)
{
$settings
=
$this
->
normalizeSettings
(
$settings
);
$typeDef
=
$this
->
getTypeDef
(
$settings
[
self
::
PARAM_TYPE
]
);
if
(
!
$typeDef
)
{
throw
new
DomainException
(
"Param $name's type is unknown - {$settings[self::PARAM_TYPE]}"
);
}
$value
=
$typeDef
->
getValue
(
$name
,
$settings
,
$options
);
if
(
$value
!==
null
)
{
if
(
!
empty
(
$settings
[
self
::
PARAM_SENSITIVE
]
)
)
{
$strValue
=
$typeDef
->
stringifyValue
(
$name
,
$value
,
$settings
,
$options
);
$this
->
callbacks
->
recordCondition
(
DataMessageValue
::
new
(
'paramvalidator-param-sensitive'
,
[],
'param-sensitive'
)
->
plaintextParams
(
$name
,
$strValue
),
$name
,
$value
,
$settings
,
$options
);
}
// Set a warning if a deprecated parameter has been passed
if
(
!
empty
(
$settings
[
self
::
PARAM_DEPRECATED
]
)
)
{
$strValue
=
$typeDef
->
stringifyValue
(
$name
,
$value
,
$settings
,
$options
);
$this
->
callbacks
->
recordCondition
(
DataMessageValue
::
new
(
'paramvalidator-param-deprecated'
,
[],
'param-deprecated'
)
->
plaintextParams
(
$name
,
$strValue
),
$name
,
$value
,
$settings
,
$options
);
}
}
elseif
(
isset
(
$settings
[
self
::
PARAM_DEFAULT
]
)
)
{
$value
=
$settings
[
self
::
PARAM_DEFAULT
];
$options
[
'is-default'
]
=
true
;
}
return
$this
->
validateValue
(
$name
,
$value
,
$settings
,
$options
);
}
/**
* Validate a parameter value using a settings array
*
* @param string $name Parameter name
* @param null|mixed $value Parameter value
* @param array|mixed $settings Default value or an array of settings
* using PARAM_* constants.
* @param array $options Options array, passed through to the TypeDef and Callbacks.
* - An additional option, 'values-list', will be set when processing the
* values of a multi-valued parameter.
* @return mixed Validated parameter value(s)
* @throws ValidationException if the value is invalid
*/
public
function
validateValue
(
$name
,
$value
,
$settings
,
array
$options
=
[]
)
{
$settings
=
$this
->
normalizeSettings
(
$settings
);
$typeDef
=
$this
->
getTypeDef
(
$settings
[
self
::
PARAM_TYPE
]
);
if
(
!
$typeDef
)
{
throw
new
DomainException
(
"Param $name's type is unknown - {$settings[self::PARAM_TYPE]}"
);
}
if
(
$value
===
null
)
{
if
(
!
empty
(
$settings
[
self
::
PARAM_REQUIRED
]
)
)
{
throw
new
ValidationException
(
DataMessageValue
::
new
(
'paramvalidator-missingparam'
,
[],
'missingparam'
)
->
plaintextParams
(
$name
),
$name
,
$value
,
$settings
);
}
return
null
;
}
// Non-multi
if
(
empty
(
$settings
[
self
::
PARAM_ISMULTI
]
)
)
{
if
(
is_string
(
$value
)
&&
substr
(
$value
,
0
,
1
)
===
"
\x
1f"
)
{
throw
new
ValidationException
(
DataMessageValue
::
new
(
'paramvalidator-notmulti'
,
[],
'badvalue'
)
->
plaintextParams
(
$name
,
$value
),
$name
,
$value
,
$settings
);
}
// T326764: If the type of the actual param value is different from
// the type that is defined via getParamSettings(), throw an exception
// because this is a type to value mismatch.
if
(
is_array
(
$value
)
&&
!
$typeDef
->
supportsArrays
()
)
{
throw
new
ValidationException
(
DataMessageValue
::
new
(
'paramvalidator-notmulti'
,
[],
'badvalue'
)
->
plaintextParams
(
$name
,
gettype
(
$value
)
),
$name
,
$value
,
$settings
);
}
return
$typeDef
->
validate
(
$name
,
$value
,
$settings
,
$options
);
}
// Split the multi-value and validate each parameter
$limit1
=
$settings
[
self
::
PARAM_ISMULTI_LIMIT1
]
??
$this
->
ismultiLimit1
;
$limit2
=
max
(
$limit1
,
$settings
[
self
::
PARAM_ISMULTI_LIMIT2
]
??
$this
->
ismultiLimit2
);
if
(
is_array
(
$value
)
)
{
$valuesList
=
$value
;
}
elseif
(
$options
[
self
::
OPT_ENFORCE_JSON_TYPES
]
??
false
)
{
throw
new
ValidationException
(
DataMessageValue
::
new
(
'paramvalidator-multivalue-must-be-array'
,
[],
'multivalue-must-be-array'
)->
plaintextParams
(
$name
),
$name
,
$value
,
$settings
);
}
else
{
$valuesList
=
self
::
explodeMultiValue
(
$value
,
$limit2
+
1
);
}
// Handle PARAM_ALL
$enumValues
=
$typeDef
->
getEnumValues
(
$name
,
$settings
,
$options
);
if
(
is_array
(
$enumValues
)
&&
isset
(
$settings
[
self
::
PARAM_ALL
]
)
&&
count
(
$valuesList
)
===
1
)
{
$allValue
=
is_string
(
$settings
[
self
::
PARAM_ALL
]
)
?
$settings
[
self
::
PARAM_ALL
]
:
self
::
ALL_DEFAULT_STRING
;
if
(
$valuesList
[
0
]
===
$allValue
)
{
return
$enumValues
;
}
}
// Avoid checking useHighLimits() unless it's actually necessary
$sizeLimit
=
(
$limit2
>
$limit1
&&
count
(
$valuesList
)
>
$limit1
&&
$this
->
callbacks
->
useHighLimits
(
$options
)
)
?
$limit2
:
$limit1
;
if
(
count
(
$valuesList
)
>
$sizeLimit
)
{
throw
new
ValidationException
(
DataMessageValue
::
new
(
'paramvalidator-toomanyvalues'
,
[],
'toomanyvalues'
,
[
'parameter'
=>
$name
,
'limit'
=>
$sizeLimit
,
'lowlimit'
=>
$limit1
,
'highlimit'
=>
$limit2
,
]
)->
plaintextParams
(
$name
)->
numParams
(
$sizeLimit
),
$name
,
$valuesList
,
$settings
);
}
$options
[
'values-list'
]
=
$valuesList
;
$validValues
=
[];
$invalidValues
=
[];
foreach
(
$valuesList
as
$v
)
{
try
{
$validValues
[]
=
$typeDef
->
validate
(
$name
,
$v
,
$settings
,
$options
);
}
catch
(
ValidationException
$ex
)
{
if
(
$ex
->
getFailureMessage
()->
getCode
()
!==
'badvalue'
||
empty
(
$settings
[
self
::
PARAM_IGNORE_UNRECOGNIZED_VALUES
]
)
)
{
throw
$ex
;
}
$invalidValues
[]
=
$v
;
}
}
if
(
$invalidValues
)
{
if
(
is_array
(
$value
)
)
{
$value
=
self
::
implodeMultiValue
(
$value
);
}
$this
->
callbacks
->
recordCondition
(
DataMessageValue
::
new
(
'paramvalidator-unrecognizedvalues'
,
[],
'unrecognizedvalues'
,
[
'values'
=>
$invalidValues
,
]
)
->
plaintextParams
(
$name
,
$value
)
->
commaListParams
(
array_map
(
static
function
(
$v
)
{
return
new
ScalarParam
(
ParamType
::
PLAINTEXT
,
$v
);
},
$invalidValues
)
)
->
numParams
(
count
(
$invalidValues
)
),
$name
,
$value
,
$settings
,
$options
);
}
// Throw out duplicates if requested
if
(
empty
(
$settings
[
self
::
PARAM_ALLOW_DUPLICATES
]
)
)
{
$validValues
=
array_values
(
array_unique
(
$validValues
)
);
}
return
$validValues
;
}
/**
* Describe parameter settings in a machine-readable format.
*
* @param string $name Parameter name.
* @param array|mixed $settings Default value or an array of settings
* using PARAM_* constants.
* @param array $options Options array.
* @return array
*/
public
function
getParamInfo
(
$name
,
$settings
,
array
$options
)
{
$settings
=
$this
->
normalizeSettings
(
$settings
);
$typeDef
=
$this
->
getTypeDef
(
$settings
[
self
::
PARAM_TYPE
]
);
$info
=
[];
$info
[
'type'
]
=
$settings
[
self
::
PARAM_TYPE
];
$info
[
'required'
]
=
!
empty
(
$settings
[
self
::
PARAM_REQUIRED
]
);
if
(
!
empty
(
$settings
[
self
::
PARAM_DEPRECATED
]
)
)
{
$info
[
'deprecated'
]
=
true
;
}
if
(
!
empty
(
$settings
[
self
::
PARAM_SENSITIVE
]
)
)
{
$info
[
'sensitive'
]
=
true
;
}
if
(
isset
(
$settings
[
self
::
PARAM_DEFAULT
]
)
)
{
$info
[
'default'
]
=
$settings
[
self
::
PARAM_DEFAULT
];
}
$info
[
'multi'
]
=
!
empty
(
$settings
[
self
::
PARAM_ISMULTI
]
);
if
(
$info
[
'multi'
]
)
{
$info
[
'lowlimit'
]
=
$settings
[
self
::
PARAM_ISMULTI_LIMIT1
]
??
$this
->
ismultiLimit1
;
$info
[
'highlimit'
]
=
max
(
$info
[
'lowlimit'
],
$settings
[
self
::
PARAM_ISMULTI_LIMIT2
]
??
$this
->
ismultiLimit2
);
$info
[
'limit'
]
=
$info
[
'highlimit'
]
>
$info
[
'lowlimit'
]
&&
$this
->
callbacks
->
useHighLimits
(
$options
)
?
$info
[
'highlimit'
]
:
$info
[
'lowlimit'
];
if
(
!
empty
(
$settings
[
self
::
PARAM_ALLOW_DUPLICATES
]
)
)
{
$info
[
'allowsduplicates'
]
=
true
;
}
$allSpecifier
=
$settings
[
self
::
PARAM_ALL
]
??
false
;
if
(
$allSpecifier
!==
false
)
{
if
(
!
is_string
(
$allSpecifier
)
)
{
$allSpecifier
=
self
::
ALL_DEFAULT_STRING
;
}
$info
[
'allspecifier'
]
=
$allSpecifier
;
}
}
if
(
$typeDef
)
{
$info
=
array_merge
(
$info
,
$typeDef
->
getParamInfo
(
$name
,
$settings
,
$options
)
);
}
// Filter out nulls (strictly)
return
array_filter
(
$info
,
static
function
(
$v
)
{
return
$v
!==
null
;
}
);
}
/**
* Describe parameter settings in human-readable format
*
* @param string $name Parameter name being described.
* @param array|mixed $settings Default value or an array of settings
* using PARAM_* constants.
* @param array $options Options array.
* @return MessageValue[]
*/
public
function
getHelpInfo
(
$name
,
$settings
,
array
$options
)
{
$settings
=
$this
->
normalizeSettings
(
$settings
);
$typeDef
=
$this
->
getTypeDef
(
$settings
[
self
::
PARAM_TYPE
]
);
// Define ordering. Some are overwritten below, some expected from the TypeDef
$info
=
[
self
::
PARAM_DEPRECATED
=>
null
,
self
::
PARAM_REQUIRED
=>
null
,
self
::
PARAM_SENSITIVE
=>
null
,
self
::
PARAM_TYPE
=>
null
,
self
::
PARAM_ISMULTI
=>
null
,
self
::
PARAM_ISMULTI_LIMIT1
=>
null
,
self
::
PARAM_ALL
=>
null
,
self
::
PARAM_DEFAULT
=>
null
,
];
if
(
!
empty
(
$settings
[
self
::
PARAM_DEPRECATED
]
)
)
{
$info
[
self
::
PARAM_DEPRECATED
]
=
MessageValue
::
new
(
'paramvalidator-help-deprecated'
);
}
if
(
!
empty
(
$settings
[
self
::
PARAM_REQUIRED
]
)
)
{
$info
[
self
::
PARAM_REQUIRED
]
=
MessageValue
::
new
(
'paramvalidator-help-required'
);
}
if
(
!
empty
(
$settings
[
self
::
PARAM_ISMULTI
]
)
)
{
$info
[
self
::
PARAM_ISMULTI
]
=
MessageValue
::
new
(
'paramvalidator-help-multi-separate'
);
$lowcount
=
$settings
[
self
::
PARAM_ISMULTI_LIMIT1
]
??
$this
->
ismultiLimit1
;
$highcount
=
max
(
$lowcount
,
$settings
[
self
::
PARAM_ISMULTI_LIMIT2
]
??
$this
->
ismultiLimit2
);
$values
=
$typeDef
?
$typeDef
->
getEnumValues
(
$name
,
$settings
,
$options
)
:
null
;
if
(
// Only mention the limits if they're likely to matter.
$values
===
null
||
count
(
$values
)
>
$lowcount
||
!
empty
(
$settings
[
self
::
PARAM_ALLOW_DUPLICATES
]
)
)
{
if
(
$highcount
>
$lowcount
)
{
$info
[
self
::
PARAM_ISMULTI_LIMIT1
]
=
MessageValue
::
new
(
'paramvalidator-help-multi-max'
)
->
numParams
(
$lowcount
,
$highcount
);
}
else
{
$info
[
self
::
PARAM_ISMULTI_LIMIT1
]
=
MessageValue
::
new
(
'paramvalidator-help-multi-max-simple'
)
->
numParams
(
$lowcount
);
}
}
$allSpecifier
=
$settings
[
self
::
PARAM_ALL
]
??
false
;
if
(
$allSpecifier
!==
false
)
{
if
(
!
is_string
(
$allSpecifier
)
)
{
$allSpecifier
=
self
::
ALL_DEFAULT_STRING
;
}
$info
[
self
::
PARAM_ALL
]
=
MessageValue
::
new
(
'paramvalidator-help-multi-all'
)
->
plaintextParams
(
$allSpecifier
);
}
}
if
(
isset
(
$settings
[
self
::
PARAM_DEFAULT
]
)
&&
$typeDef
)
{
$value
=
$typeDef
->
stringifyValue
(
$name
,
$settings
[
self
::
PARAM_DEFAULT
],
$settings
,
$options
);
if
(
$value
===
''
)
{
$info
[
self
::
PARAM_DEFAULT
]
=
MessageValue
::
new
(
'paramvalidator-help-default-empty'
);
}
elseif
(
$value
!==
null
)
{
$info
[
self
::
PARAM_DEFAULT
]
=
MessageValue
::
new
(
'paramvalidator-help-default'
)
->
plaintextParams
(
$value
);
}
}
if
(
$typeDef
)
{
$info
=
array_merge
(
$info
,
$typeDef
->
getHelpInfo
(
$name
,
$settings
,
$options
)
);
}
// Put the default at the very end (the TypeDef may have added extra messages)
$default
=
$info
[
self
::
PARAM_DEFAULT
];
unset
(
$info
[
self
::
PARAM_DEFAULT
]
);
$info
[
self
::
PARAM_DEFAULT
]
=
$default
;
// Filter out nulls
return
array_filter
(
$info
);
}
/**
* Split a multi-valued parameter string, like explode()
*
* Note that, unlike explode(), this will return an empty array when given
* an empty string.
*
* @param string $value
* @param int $limit
* @return string[]
*/
public
static
function
explodeMultiValue
(
$value
,
$limit
)
{
if
(
$value
===
''
||
$value
===
"
\x
1f"
)
{
return
[];
}
if
(
substr
(
$value
,
0
,
1
)
===
"
\x
1f"
)
{
$sep
=
"
\x
1f"
;
$value
=
substr
(
$value
,
1
);
}
else
{
$sep
=
'|'
;
}
return
explode
(
$sep
,
$value
,
$limit
);
}
/**
* Implode an array as a multi-valued parameter string, like implode()
*
* @param array $value
* @return string
*/
public
static
function
implodeMultiValue
(
array
$value
)
{
if
(
$value
===
[
''
]
)
{
// There's no value that actually returns a single empty string.
// Best we can do is this that returns two, which will be deduplicated to one.
return
'|'
;
}
foreach
(
$value
as
$v
)
{
if
(
strpos
(
$v
,
'|'
)
!==
false
)
{
return
"
\x
1f"
.
implode
(
"
\x
1f"
,
$value
);
}
}
return
implode
(
'|'
,
$value
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 21:57 (1 d, 5 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
35/2c/2cfbd7f2bcc78ce5d11db1c1f817
Default Alt Text
ParamValidator.php (29 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment