MediaWiki REL1_34
ParamValidator.php
Go to the documentation of this file.
1<?php
2
4
5use DomainException;
6use InvalidArgumentException;
7use Wikimedia\Assert\Assert;
8use Wikimedia\ObjectFactory;
9
39
59 const PARAM_DEFAULT = 'param-default';
60
67 const PARAM_TYPE = 'param-type';
68
75 const PARAM_REQUIRED = 'param-required';
76
101 const PARAM_ISMULTI = 'param-ismulti';
102
113 const PARAM_ISMULTI_LIMIT1 = 'param-ismulti-limit1';
114
126 const PARAM_ISMULTI_LIMIT2 = 'param-ismulti-limit2';
127
136 const PARAM_ALL = 'param-all';
137
144 const PARAM_ALLOW_DUPLICATES = 'param-allow-duplicates';
145
152 const PARAM_SENSITIVE = 'param-sensitive';
153
160 const PARAM_DEPRECATED = 'param-deprecated';
161
168 const PARAM_IGNORE_INVALID_VALUES = 'param-ignore-invalid-values';
169
174
176 public static $STANDARD_TYPES = [
177 'boolean' => [ 'class' => TypeDef\BooleanDef::class ],
178 'checkbox' => [ 'class' => TypeDef\PresenceBooleanDef::class ],
179 'integer' => [ 'class' => TypeDef\IntegerDef::class ],
180 'limit' => [ 'class' => TypeDef\LimitDef::class ],
181 'float' => [ 'class' => TypeDef\FloatDef::class ],
182 'double' => [ 'class' => TypeDef\FloatDef::class ],
183 'string' => [ 'class' => TypeDef\StringDef::class ],
184 'password' => [ 'class' => TypeDef\PasswordDef::class ],
185 'NULL' => [
186 'class' => TypeDef\StringDef::class,
187 'args' => [ [
188 'allowEmptyWhenRequired' => true,
189 ] ],
190 ],
191 'timestamp' => [ 'class' => TypeDef\TimestampDef::class ],
192 'upload' => [ 'class' => TypeDef\UploadDef::class ],
193 'enum' => [ 'class' => TypeDef\EnumDef::class ],
194 ];
195
197 private $callbacks;
198
201
203 private $typeDefs = [];
204
207
210
220 public function __construct(
222 ObjectFactory $objectFactory,
223 array $options = []
224 ) {
225 $this->callbacks = $callbacks;
226 $this->objectFactory = $objectFactory;
227
228 $this->addTypeDefs( $options['typeDefs'] ?? self::$STANDARD_TYPES );
229 $this->ismultiLimit1 = $options['ismultiLimits'][0] ?? 50;
230 $this->ismultiLimit2 = $options['ismultiLimits'][1] ?? 500;
231 }
232
237 public function knownTypes() {
238 return array_keys( $this->typeDefs );
239 }
240
247 public function addTypeDefs( array $typeDefs ) {
248 foreach ( $typeDefs as $name => $def ) {
249 $this->addTypeDef( $name, $def );
250 }
251 }
252
266 public function addTypeDef( $name, $typeDef ) {
267 Assert::parameterType(
268 implode( '|', [ TypeDef::class, 'array' ] ),
269 $typeDef,
270 '$typeDef'
271 );
272
273 if ( isset( $this->typeDefs[$name] ) ) {
274 throw new InvalidArgumentException( "Type '$name' is already registered" );
275 }
276 $this->typeDefs[$name] = $typeDef;
277 }
278
285 public function overrideTypeDef( $name, $typeDef ) {
286 Assert::parameterType(
287 implode( '|', [ TypeDef::class, 'array', 'null' ] ),
288 $typeDef,
289 '$typeDef'
290 );
291
292 if ( $typeDef === null ) {
293 unset( $this->typeDefs[$name] );
294 } else {
295 $this->typeDefs[$name] = $typeDef;
296 }
297 }
298
304 public function hasTypeDef( $name ) {
305 return isset( $this->typeDefs[$name] );
306 }
307
313 public function getTypeDef( $type ) {
314 if ( is_array( $type ) ) {
315 $type = 'enum';
316 }
317
318 if ( !isset( $this->typeDefs[$type] ) ) {
319 return null;
320 }
321
322 $def = $this->typeDefs[$type];
323 if ( !$def instanceof TypeDef ) {
324 $def = $this->objectFactory->createObject( $def, [
325 'extraArgs' => [ $this->callbacks ],
326 'assertClass' => TypeDef::class,
327 ] );
328 $this->typeDefs[$type] = $def;
329 }
330
331 return $def;
332 }
333
340 public function normalizeSettings( $settings ) {
341 // Shorthand
342 if ( !is_array( $settings ) ) {
343 $settings = [
344 self::PARAM_DEFAULT => $settings,
345 ];
346 }
347
348 // When type is not given, determine it from the type of the PARAM_DEFAULT
349 if ( !isset( $settings[self::PARAM_TYPE] ) ) {
350 $settings[self::PARAM_TYPE] = gettype( $settings[self::PARAM_DEFAULT] ?? null );
351 }
352
353 $typeDef = $this->getTypeDef( $settings[self::PARAM_TYPE] );
354 if ( $typeDef ) {
355 $settings = $typeDef->normalizeSettings( $settings );
356 }
357
358 return $settings;
359 }
360
371 public function getValue( $name, $settings, array $options = [] ) {
372 $settings = $this->normalizeSettings( $settings );
373
374 $typeDef = $this->getTypeDef( $settings[self::PARAM_TYPE] );
375 if ( !$typeDef ) {
376 throw new DomainException(
377 "Param $name's type is unknown - {$settings[self::PARAM_TYPE]}"
378 );
379 }
380
381 $value = $typeDef->getValue( $name, $settings, $options );
382
383 if ( $value !== null ) {
384 if ( !empty( $settings[self::PARAM_SENSITIVE] ) ) {
385 $this->callbacks->recordCondition(
386 new ValidationException( $name, $value, $settings, 'param-sensitive', [] ),
387 $options
388 );
389 }
390
391 // Set a warning if a deprecated parameter has been passed
392 if ( !empty( $settings[self::PARAM_DEPRECATED] ) ) {
393 $this->callbacks->recordCondition(
394 new ValidationException( $name, $value, $settings, 'param-deprecated', [] ),
395 $options
396 );
397 }
398 } elseif ( isset( $settings[self::PARAM_DEFAULT] ) ) {
399 $value = $settings[self::PARAM_DEFAULT];
400 }
401
402 return $this->validateValue( $name, $value, $settings, $options );
403 }
404
418 public function validateValue( $name, $value, $settings, array $options = [] ) {
419 $settings = $this->normalizeSettings( $settings );
420
421 $typeDef = $this->getTypeDef( $settings[self::PARAM_TYPE] );
422 if ( !$typeDef ) {
423 throw new DomainException(
424 "Param $name's type is unknown - {$settings[self::PARAM_TYPE]}"
425 );
426 }
427
428 if ( $value === null ) {
429 if ( !empty( $settings[self::PARAM_REQUIRED] ) ) {
430 throw new ValidationException( $name, $value, $settings, 'missingparam', [] );
431 }
432 return null;
433 }
434
435 // Non-multi
436 if ( empty( $settings[self::PARAM_ISMULTI] ) ) {
437 return $typeDef->validate( $name, $value, $settings, $options );
438 }
439
440 // Split the multi-value and validate each parameter
441 $limit1 = $settings[self::PARAM_ISMULTI_LIMIT1] ?? $this->ismultiLimit1;
442 $limit2 = $settings[self::PARAM_ISMULTI_LIMIT2] ?? $this->ismultiLimit2;
443 $valuesList = is_array( $value ) ? $value : self::explodeMultiValue( $value, $limit2 + 1 );
444
445 // Handle PARAM_ALL
446 $enumValues = $typeDef->getEnumValues( $name, $settings, $options );
447 if ( is_array( $enumValues ) && isset( $settings[self::PARAM_ALL] ) &&
448 count( $valuesList ) === 1
449 ) {
450 $allValue = is_string( $settings[self::PARAM_ALL] )
451 ? $settings[self::PARAM_ALL]
453 if ( $valuesList[0] === $allValue ) {
454 return $enumValues;
455 }
456 }
457
458 // Avoid checking useHighLimits() unless it's actually necessary
459 $sizeLimit = count( $valuesList ) > $limit1 && $this->callbacks->useHighLimits( $options )
460 ? $limit2
461 : $limit1;
462 if ( count( $valuesList ) > $sizeLimit ) {
463 throw new ValidationException( $name, $valuesList, $settings, 'toomanyvalues', [
464 'limit' => $sizeLimit
465 ] );
466 }
467
468 $options['values-list'] = $valuesList;
469 $validValues = [];
470 $invalidValues = [];
471 foreach ( $valuesList as $v ) {
472 try {
473 $validValues[] = $typeDef->validate( $name, $v, $settings, $options );
474 } catch ( ValidationException $ex ) {
475 if ( empty( $settings[self::PARAM_IGNORE_INVALID_VALUES] ) ) {
476 throw $ex;
477 }
478 $invalidValues[] = $v;
479 }
480 }
481 if ( $invalidValues ) {
482 $this->callbacks->recordCondition(
483 new ValidationException( $name, $value, $settings, 'unrecognizedvalues', [
484 'values' => $invalidValues,
485 ] ),
486 $options
487 );
488 }
489
490 // Throw out duplicates if requested
491 if ( empty( $settings[self::PARAM_ALLOW_DUPLICATES] ) ) {
492 $validValues = array_values( array_unique( $validValues ) );
493 }
494
495 return $validValues;
496 }
497
508 public static function explodeMultiValue( $value, $limit ) {
509 if ( $value === '' || $value === "\x1f" ) {
510 return [];
511 }
512
513 if ( substr( $value, 0, 1 ) === "\x1f" ) {
514 $sep = "\x1f";
515 $value = substr( $value, 1 );
516 } else {
517 $sep = '|';
518 }
519
520 return explode( $sep, $value, $limit );
521 }
522
523}
Service for formatting and validating API parameters.
TypeDef array[] $typeDefs
Map parameter type names to TypeDef objects or ObjectFactory specs.
__construct(Callbacks $callbacks, ObjectFactory $objectFactory, array $options=[])
getValue( $name, $settings, array $options=[])
Fetch and valiate a parameter value using a settings array.
normalizeSettings( $settings)
Normalize a parameter settings array.
const PARAM_ALLOW_DUPLICATES
(bool) Allow the same value to be set more than once when PARAM_ISMULTI is true?
static $STANDARD_TYPES
A list of standard type names and types that may be passed as $typeDefs to __construct().
getTypeDef( $type)
Get the TypeDef for a type.
const PARAM_ISMULTI
(bool) Indicate that the parameter is multi-valued.
addTypeDefs(array $typeDefs)
Register multiple type handlers.
hasTypeDef( $name)
Test if a type is registered.
const PARAM_ISMULTI_LIMIT2
(int) Maximum number of multi-valued parameter values allowed for users allowed high limits.
int $ismultiLimit1
Default values for PARAM_ISMULTI_LIMIT1.
const ALL_DEFAULT_STRING
Magic "all values" value when PARAM_ALL is true.
validateValue( $name, $value, $settings, array $options=[])
Valiate a parameter value using a settings array.
const PARAM_ISMULTI_LIMIT1
(int) Maximum number of multi-valued parameter values allowed
const PARAM_DEFAULT
(mixed) Default value of the parameter.
const PARAM_DEPRECATED
(bool) Indicate that a deprecated parameter was used.
const PARAM_TYPE
(string|array) Type of the parameter.
const PARAM_SENSITIVE
(bool) Indicate that the parameter's value should not be logged.
const PARAM_REQUIRED
(bool) Indicate that the parameter is required.
addTypeDef( $name, $typeDef)
Register a type handler.
static explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
const PARAM_IGNORE_INVALID_VALUES
(bool) Whether to ignore invalid values.
int $ismultiLimit2
Default values for PARAM_ISMULTI_LIMIT2.
const PARAM_ALL
(bool|string) Whether a magic "all values" value exists for multi-valued enumerated types,...
overrideTypeDef( $name, $typeDef)
Register a type handler, overriding any existing handler.
Base definition for ParamValidator types.
Definition TypeDef.php:15
Interface defining callbacks needed by ParamValidator.
Definition Callbacks.php:20