6use InvalidArgumentException;
7use Wikimedia\Assert\Assert;
12use Wikimedia\ObjectFactory;
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 ],
186 'class' => TypeDef\StringDef::class,
188 'allowEmptyWhenRequired' =>
true,
191 'timestamp' => [
'class' => TypeDef\TimestampDef::class ],
192 'upload' => [
'class' => TypeDef\UploadDef::class ],
193 'enum' => [
'class' => TypeDef\EnumDef::class ],
194 'expiry' => [
'class' => TypeDef\ExpiryDef::class ],
229 $this->
addTypeDefs( $options[
'typeDefs'] ?? self::$STANDARD_TYPES );
230 $this->ismultiLimit1 = $options[
'ismultiLimits'][0] ?? 50;
231 $this->ismultiLimit2 = $options[
'ismultiLimits'][1] ?? 500;
239 return array_keys( $this->typeDefs );
268 Assert::parameterType(
269 implode(
'|', [ TypeDef::class,
'array' ] ),
274 if ( isset( $this->typeDefs[$name] ) ) {
275 throw new InvalidArgumentException(
"Type '$name' is already registered" );
277 $this->typeDefs[$name] = $typeDef;
287 Assert::parameterType(
288 implode(
'|', [ TypeDef::class,
'array',
'null' ] ),
293 if ( $typeDef ===
null ) {
294 unset( $this->typeDefs[$name] );
296 $this->typeDefs[$name] = $typeDef;
306 return isset( $this->typeDefs[$name] );
315 if ( is_array(
$type ) ) {
319 if ( !isset( $this->typeDefs[
$type] ) ) {
323 $def = $this->typeDefs[
$type];
324 if ( !$def instanceof
TypeDef ) {
325 $def = $this->objectFactory->createObject( $def, [
326 'extraArgs' => [ $this->callbacks ],
327 'assertClass' => TypeDef::class,
329 $this->typeDefs[
$type] = $def;
342 if ( !is_array( $settings ) ) {
344 self::PARAM_DEFAULT => $settings,
349 if ( !isset( $settings[self::PARAM_TYPE] ) ) {
350 $settings[
self::PARAM_TYPE] = gettype( $settings[self::PARAM_DEFAULT] ??
null );
365 $typeDef = $this->
getTypeDef( $settings[self::PARAM_TYPE] );
367 $settings = $typeDef->normalizeSettings( $settings );
391 public function checkSettings(
string $name, $settings, array $options ) : array {
402 if ( !is_string(
$type ) && !is_array(
$type ) ) {
405 $typeDef = $this->
getTypeDef( $settings[self::PARAM_TYPE] );
407 if ( is_array(
$type ) ) {
414 if ( isset( $settings[self::PARAM_DEFAULT] ) ) {
417 $name, $settings[self::PARAM_DEFAULT], $settings, [
'is-default' =>
true ] + $options
419 }
catch ( ValidationException $ex ) {
421 . $ex->getFailureMessage()->getCode() .
')';
425 if ( !is_bool( $settings[self::PARAM_REQUIRED] ??
false ) ) {
427 . gettype( $settings[self::PARAM_REQUIRED] );
430 if ( !is_bool( $settings[self::PARAM_ISMULTI] ??
false ) ) {
432 . gettype( $settings[self::PARAM_ISMULTI] );
435 if ( !empty( $settings[self::PARAM_ISMULTI] ) ) {
436 $allowedKeys = array_merge( $allowedKeys, [
437 self::PARAM_ISMULTI_LIMIT1, self::PARAM_ISMULTI_LIMIT2,
438 self::PARAM_ALL, self::PARAM_ALLOW_DUPLICATES
443 if ( !is_int( $limit1 ) ) {
445 . gettype( $settings[self::PARAM_ISMULTI_LIMIT1] );
446 } elseif ( $limit1 <= 0 ) {
448 "PARAM_ISMULTI_LIMIT1 must be greater than 0, got $limit1";
450 if ( !is_int( $limit2 ) ) {
452 . gettype( $settings[self::PARAM_ISMULTI_LIMIT2] );
453 } elseif ( $limit2 < $limit1 ) {
455 'PARAM_ISMULTI_LIMIT2 must be greater than or equal to PARAM_ISMULTI_LIMIT1, but '
456 .
"$limit2 < $limit1";
460 if ( !is_string( $all ) && !is_bool( $all ) ) {
461 $issues[
self::PARAM_ALL] =
'PARAM_ALL must be a string or boolean, got ' . gettype( $all );
462 } elseif ( $all !==
false && $typeDef ) {
463 if ( $all ===
true ) {
466 $values = $typeDef->getEnumValues( $name, $settings, $options );
467 if ( !is_array( $values ) ) {
468 $issues[
self::PARAM_ALL] =
'PARAM_ALL cannot be used with non-enumerated types';
469 } elseif ( in_array( $all, $values,
true ) ) {
470 $issues[
self::PARAM_ALL] =
'Value for PARAM_ALL conflicts with an enumerated value';
474 if ( !is_bool( $settings[self::PARAM_ALLOW_DUPLICATES] ??
false ) ) {
476 . gettype( $settings[self::PARAM_ALLOW_DUPLICATES] );
480 if ( !is_bool( $settings[self::PARAM_SENSITIVE] ??
false ) ) {
482 . gettype( $settings[self::PARAM_SENSITIVE] );
485 if ( !is_bool( $settings[self::PARAM_DEPRECATED] ??
false ) ) {
487 . gettype( $settings[self::PARAM_DEPRECATED] );
490 if ( !is_bool( $settings[self::PARAM_IGNORE_UNRECOGNIZED_VALUES] ??
false ) ) {
492 .
'boolean, got ' . gettype( $settings[self::PARAM_IGNORE_UNRECOGNIZED_VALUES] );
495 $ret = [
'issues' => $issues,
'allowedKeys' => $allowedKeys,
'messages' => $messages ];
497 $ret = $typeDef->checkSettings( $name, $settings, $options, $ret );
514 public function getValue( $name, $settings, array $options = [] ) {
515 $settings = $this->normalizeSettings( $settings );
517 $typeDef = $this->getTypeDef( $settings[self::PARAM_TYPE] );
519 throw new DomainException(
520 "Param $name's type is unknown - {$settings[self::PARAM_TYPE]}"
524 $value = $typeDef->getValue( $name, $settings, $options );
526 if ( $value !==
null ) {
527 if ( !empty( $settings[self::PARAM_SENSITIVE] ) ) {
528 $strValue = $typeDef->stringifyValue( $name, $value, $settings, $options );
529 $this->callbacks->recordCondition(
530 DataMessageValue::new(
'paramvalidator-param-sensitive', [],
'param-sensitive' )
531 ->plaintextParams( $name, $strValue ),
532 $name, $value, $settings, $options
537 if ( !empty( $settings[self::PARAM_DEPRECATED] ) ) {
538 $strValue = $typeDef->stringifyValue( $name, $value, $settings, $options );
539 $this->callbacks->recordCondition(
540 DataMessageValue::new(
'paramvalidator-param-deprecated', [],
'param-deprecated' )
541 ->plaintextParams( $name, $strValue ),
542 $name, $value, $settings, $options
545 } elseif ( isset( $settings[self::PARAM_DEFAULT] ) ) {
546 $value = $settings[self::PARAM_DEFAULT];
547 $options[
'is-default'] =
true;
550 return $this->validateValue( $name, $value, $settings, $options );
566 public function validateValue( $name, $value, $settings, array $options = [] ) {
567 $settings = $this->normalizeSettings( $settings );
569 $typeDef = $this->getTypeDef( $settings[self::PARAM_TYPE] );
571 throw new DomainException(
572 "Param $name's type is unknown - {$settings[self::PARAM_TYPE]}"
576 if ( $value ===
null ) {
577 if ( !empty( $settings[self::PARAM_REQUIRED] ) ) {
579 DataMessageValue::new(
'paramvalidator-missingparam', [],
'missingparam' )
580 ->plaintextParams( $name ),
581 $name, $value, $settings
588 if ( empty( $settings[self::PARAM_ISMULTI] ) ) {
589 if ( is_string( $value ) && substr( $value, 0, 1 ) ===
"\x1f" ) {
591 DataMessageValue::new(
'paramvalidator-notmulti', [],
'badvalue' )
592 ->plaintextParams( $name, $value ),
593 $name, $value, $settings
596 return $typeDef->validate( $name, $value, $settings, $options );
600 $limit1 = $settings[self::PARAM_ISMULTI_LIMIT1] ?? $this->ismultiLimit1;
601 $limit2 = max( $limit1, $settings[self::PARAM_ISMULTI_LIMIT2] ?? $this->ismultiLimit2 );
602 $valuesList = is_array( $value ) ? $value : self::explodeMultiValue( $value, $limit2 + 1 );
605 $enumValues = $typeDef->getEnumValues( $name, $settings, $options );
606 if ( is_array( $enumValues ) && isset( $settings[self::PARAM_ALL] ) &&
607 count( $valuesList ) === 1
609 $allValue = is_string( $settings[self::PARAM_ALL] )
610 ? $settings[self::PARAM_ALL]
611 : self::ALL_DEFAULT_STRING;
612 if ( $valuesList[0] === $allValue ) {
619 $limit2 > $limit1 && count( $valuesList ) > $limit1 &&
620 $this->callbacks->useHighLimits( $options )
621 ) ? $limit2 : $limit1;
622 if ( count( $valuesList ) > $sizeLimit ) {
624 DataMessageValue::new(
'paramvalidator-toomanyvalues', [],
'toomanyvalues', [
625 'limit' => $sizeLimit,
626 'lowlimit' => $limit1,
627 'highlimit' => $limit2,
628 ] )->plaintextParams( $name )->numParams( $sizeLimit ),
629 $name, $valuesList, $settings
633 $options[
'values-list'] = $valuesList;
636 foreach ( $valuesList as $v ) {
638 $validValues[] = $typeDef->validate( $name, $v, $settings, $options );
641 empty( $settings[self::PARAM_IGNORE_UNRECOGNIZED_VALUES] )
645 $invalidValues[] = $v;
648 if ( $invalidValues ) {
649 if ( is_array( $value ) ) {
650 $value = self::implodeMultiValue( $value );
652 $this->callbacks->recordCondition(
653 DataMessageValue::new(
'paramvalidator-unrecognizedvalues', [],
'unrecognizedvalues', [
654 'values' => $invalidValues,
656 ->plaintextParams( $name, $value )
657 ->commaListParams( array_map(
function ( $v ) {
659 }, $invalidValues ) )
660 ->numParams( count( $invalidValues ) ),
661 $name, $value, $settings, $options
666 if ( empty( $settings[self::PARAM_ALLOW_DUPLICATES] ) ) {
667 $validValues = array_values( array_unique( $validValues ) );
683 $settings = $this->normalizeSettings( $settings );
684 $typeDef = $this->getTypeDef( $settings[self::PARAM_TYPE] );
687 $info[
'type'] = $settings[self::PARAM_TYPE];
688 $info[
'required'] = !empty( $settings[self::PARAM_REQUIRED] );
689 if ( !empty( $settings[self::PARAM_DEPRECATED] ) ) {
690 $info[
'deprecated'] =
true;
692 if ( !empty( $settings[self::PARAM_SENSITIVE] ) ) {
693 $info[
'sensitive'] =
true;
695 if ( isset( $settings[self::PARAM_DEFAULT] ) ) {
696 $info[
'default'] = $settings[self::PARAM_DEFAULT];
698 $info[
'multi'] = !empty( $settings[self::PARAM_ISMULTI] );
699 if ( $info[
'multi'] ) {
700 $info[
'lowlimit'] = $settings[self::PARAM_ISMULTI_LIMIT1] ?? $this->ismultiLimit1;
701 $info[
'highlimit'] = max(
702 $info[
'lowlimit'], $settings[self::PARAM_ISMULTI_LIMIT2] ?? $this->ismultiLimit2
705 $info[
'highlimit'] > $info[
'lowlimit'] && $this->callbacks->useHighLimits( $options )
709 if ( !empty( $settings[self::PARAM_ALLOW_DUPLICATES] ) ) {
710 $info[
'allowsduplicates'] =
true;
713 $allSpecifier = $settings[self::PARAM_ALL] ??
false;
714 if ( $allSpecifier !==
false ) {
715 if ( !is_string( $allSpecifier ) ) {
716 $allSpecifier = self::ALL_DEFAULT_STRING;
718 $info[
'allspecifier'] = $allSpecifier;
723 $info = array_merge( $info, $typeDef->getParamInfo( $name, $settings, $options ) );
727 return array_filter( $info,
function ( $v ) {
742 $settings = $this->normalizeSettings( $settings );
743 $typeDef = $this->getTypeDef( $settings[self::PARAM_TYPE] );
747 self::PARAM_DEPRECATED =>
null,
748 self::PARAM_REQUIRED =>
null,
749 self::PARAM_SENSITIVE =>
null,
750 self::PARAM_TYPE =>
null,
751 self::PARAM_ISMULTI =>
null,
752 self::PARAM_ISMULTI_LIMIT1 =>
null,
753 self::PARAM_ALL =>
null,
754 self::PARAM_DEFAULT =>
null,
757 if ( !empty( $settings[self::PARAM_DEPRECATED] ) ) {
758 $info[self::PARAM_DEPRECATED] = MessageValue::new(
'paramvalidator-help-deprecated' );
761 if ( !empty( $settings[self::PARAM_REQUIRED] ) ) {
762 $info[self::PARAM_REQUIRED] = MessageValue::new(
'paramvalidator-help-required' );
765 if ( !empty( $settings[self::PARAM_ISMULTI] ) ) {
766 $info[self::PARAM_ISMULTI] = MessageValue::new(
'paramvalidator-help-multi-separate' );
768 $lowcount = $settings[self::PARAM_ISMULTI_LIMIT1] ?? $this->ismultiLimit1;
769 $highcount = max( $lowcount, $settings[self::PARAM_ISMULTI_LIMIT2] ?? $this->ismultiLimit2 );
770 $values = $typeDef ? $typeDef->getEnumValues( $name, $settings, $options ) :
null;
773 $values ===
null || count( $values ) > $lowcount ||
774 !empty( $settings[self::PARAM_ALLOW_DUPLICATES] )
776 if ( $highcount > $lowcount ) {
777 $info[self::PARAM_ISMULTI_LIMIT1] = MessageValue::new(
'paramvalidator-help-multi-max' )
778 ->numParams( $lowcount, $highcount );
780 $info[self::PARAM_ISMULTI_LIMIT1] = MessageValue::new(
'paramvalidator-help-multi-max-simple' )
781 ->numParams( $lowcount );
785 $allSpecifier = $settings[self::PARAM_ALL] ??
false;
786 if ( $allSpecifier !==
false ) {
787 if ( !is_string( $allSpecifier ) ) {
788 $allSpecifier = self::ALL_DEFAULT_STRING;
790 $info[self::PARAM_ALL] = MessageValue::new(
'paramvalidator-help-multi-all' )
791 ->plaintextParams( $allSpecifier );
795 if ( isset( $settings[self::PARAM_DEFAULT] ) && $typeDef ) {
796 $value = $typeDef->stringifyValue( $name, $settings[self::PARAM_DEFAULT], $settings, $options );
797 if ( $value ===
'' ) {
798 $info[self::PARAM_DEFAULT] = MessageValue::new(
'paramvalidator-help-default-empty' );
799 } elseif ( $value !==
null ) {
800 $info[self::PARAM_DEFAULT] = MessageValue::new(
'paramvalidator-help-default' )
801 ->plaintextParams( $value );
806 $info = array_merge( $info, $typeDef->getHelpInfo( $name, $settings, $options ) );
810 $default = $info[self::PARAM_DEFAULT];
811 unset( $info[self::PARAM_DEFAULT] );
812 $info[self::PARAM_DEFAULT] = $default;
815 return array_filter( $info );
829 if ( $value ===
'' || $value ===
"\x1f" ) {
833 if ( substr( $value, 0, 1 ) ===
"\x1f" ) {
835 $value = substr( $value, 1 );
840 return explode( $sep, $value, $limit );
850 if ( $value === [
'' ] ) {
856 foreach ( $value as $v ) {
857 if ( strpos( $v,
'|' ) !==
false ) {
858 return "\x1f" . implode(
"\x1f", $value );
861 return implode(
'|', $value );