MediaWiki  1.34.0
ParamValidator.php
Go to the documentation of this file.
1 <?php
2 
4 
5 use DomainException;
6 use InvalidArgumentException;
7 use Wikimedia\Assert\Assert;
8 use 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 
173  const ALL_DEFAULT_STRING = '*';
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 
200  private $objectFactory;
201 
203  private $typeDefs = [];
204 
206  private $ismultiLimit1;
207 
209  private $ismultiLimit2;
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 }
Wikimedia\ParamValidator
Definition: Callbacks.php:3
Wikimedia\ParamValidator\ParamValidator\$typeDefs
TypeDef array[] $typeDefs
Map parameter type names to TypeDef objects or ObjectFactory specs.
Definition: ParamValidator.php:203
Wikimedia\ParamValidator\ParamValidator\$STANDARD_TYPES
static $STANDARD_TYPES
A list of standard type names and types that may be passed as $typeDefs to __construct().
Definition: ParamValidator.php:176
Wikimedia\ParamValidator\ValidationException
Error reporting for ParamValidator.
Definition: ValidationException.php:15
Wikimedia\ParamValidator\Callbacks
Interface defining callbacks needed by ParamValidator.
Definition: Callbacks.php:20
Wikimedia\ParamValidator\ParamValidator\getValue
getValue( $name, $settings, array $options=[])
Fetch and valiate a parameter value using a settings array.
Definition: ParamValidator.php:371
Wikimedia\ParamValidator\ParamValidator\PARAM_ISMULTI_LIMIT1
const PARAM_ISMULTI_LIMIT1
(int) Maximum number of multi-valued parameter values allowed
Definition: ParamValidator.php:113
Wikimedia\ParamValidator\ParamValidator\explodeMultiValue
static explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition: ParamValidator.php:508
Wikimedia\ParamValidator\ParamValidator\$ismultiLimit2
int $ismultiLimit2
Default values for PARAM_ISMULTI_LIMIT2.
Definition: ParamValidator.php:209
Wikimedia\ParamValidator\ParamValidator\getTypeDef
getTypeDef( $type)
Get the TypeDef for a type.
Definition: ParamValidator.php:313
Wikimedia\ParamValidator\ParamValidator\normalizeSettings
normalizeSettings( $settings)
Normalize a parameter settings array.
Definition: ParamValidator.php:340
Wikimedia\ParamValidator\ParamValidator\PARAM_IGNORE_INVALID_VALUES
const PARAM_IGNORE_INVALID_VALUES
(bool) Whether to ignore invalid values.
Definition: ParamValidator.php:168
Wikimedia\ParamValidator\ParamValidator\PARAM_ALLOW_DUPLICATES
const PARAM_ALLOW_DUPLICATES
(bool) Allow the same value to be set more than once when PARAM_ISMULTI is true?
Definition: ParamValidator.php:144
Wikimedia\ParamValidator\ParamValidator\$objectFactory
ObjectFactory $objectFactory
Definition: ParamValidator.php:200
Wikimedia\ParamValidator\ParamValidator\ALL_DEFAULT_STRING
const ALL_DEFAULT_STRING
Magic "all values" value when PARAM_ALL is true.
Definition: ParamValidator.php:173
Wikimedia\ParamValidator\ParamValidator\$ismultiLimit1
int $ismultiLimit1
Default values for PARAM_ISMULTI_LIMIT1.
Definition: ParamValidator.php:206
Wikimedia\ParamValidator\ParamValidator\PARAM_SENSITIVE
const PARAM_SENSITIVE
(bool) Indicate that the parameter's value should not be logged.
Definition: ParamValidator.php:152
Wikimedia\ParamValidator\TypeDef
Base definition for ParamValidator types.
Definition: TypeDef.php:15
Wikimedia\ParamValidator\ParamValidator\PARAM_REQUIRED
const PARAM_REQUIRED
(bool) Indicate that the parameter is required.
Definition: ParamValidator.php:75
Wikimedia\ParamValidator\ParamValidator\PARAM_ALL
const PARAM_ALL
(bool|string) Whether a magic "all values" value exists for multi-valued enumerated types,...
Definition: ParamValidator.php:136
Wikimedia\ParamValidator\ParamValidator\overrideTypeDef
overrideTypeDef( $name, $typeDef)
Register a type handler, overriding any existing handler.
Definition: ParamValidator.php:285
Wikimedia\ParamValidator\ParamValidator\addTypeDefs
addTypeDefs(array $typeDefs)
Register multiple type handlers.
Definition: ParamValidator.php:247
Wikimedia\ParamValidator\ParamValidator\knownTypes
knownTypes()
List known type names.
Definition: ParamValidator.php:237
Wikimedia\ParamValidator\ParamValidator\$callbacks
Callbacks $callbacks
Definition: ParamValidator.php:197
Wikimedia\ParamValidator\ParamValidator\PARAM_ISMULTI
const PARAM_ISMULTI
(bool) Indicate that the parameter is multi-valued.
Definition: ParamValidator.php:101
Wikimedia\ParamValidator\ParamValidator\hasTypeDef
hasTypeDef( $name)
Test if a type is registered.
Definition: ParamValidator.php:304
Wikimedia\ParamValidator\ParamValidator\__construct
__construct(Callbacks $callbacks, ObjectFactory $objectFactory, array $options=[])
Definition: ParamValidator.php:220
Wikimedia\ParamValidator\ParamValidator\PARAM_ISMULTI_LIMIT2
const PARAM_ISMULTI_LIMIT2
(int) Maximum number of multi-valued parameter values allowed for users allowed high limits.
Definition: ParamValidator.php:126
Wikimedia\ParamValidator\ParamValidator\addTypeDef
addTypeDef( $name, $typeDef)
Register a type handler.
Definition: ParamValidator.php:266
Wikimedia\ParamValidator\ParamValidator\validateValue
validateValue( $name, $value, $settings, array $options=[])
Valiate a parameter value using a settings array.
Definition: ParamValidator.php:418
Wikimedia\ParamValidator\ParamValidator\PARAM_DEFAULT
const PARAM_DEFAULT
(mixed) Default value of the parameter.
Definition: ParamValidator.php:59
Wikimedia\ParamValidator\ParamValidator\PARAM_TYPE
const PARAM_TYPE
(string|array) Type of the parameter.
Definition: ParamValidator.php:67
Wikimedia\ParamValidator\ParamValidator\PARAM_DEPRECATED
const PARAM_DEPRECATED
(bool) Indicate that a deprecated parameter was used.
Definition: ParamValidator.php:160
Wikimedia\ParamValidator\ParamValidator
Service for formatting and validating API parameters.
Definition: ParamValidator.php:38
$type
$type
Definition: testCompression.php:48