MediaWiki  master
ApiParamValidator.php
Go to the documentation of this file.
1 <?php
2 
4 
5 use ApiBase;
6 use ApiMain;
7 use ApiMessage;
9 use MediaWiki\Message\Converter as MessageConverter;
13 use Message;
16 use Wikimedia\ObjectFactory;
28 
38 
40  private $paramValidator;
41 
44 
46  private const TYPE_DEFS = [
47  'boolean' => [ 'class' => PresenceBooleanDef::class ],
48  'enum' => [ 'class' => EnumDef::class ],
49  'expiry' => [ 'class' => ExpiryDef::class ],
50  'integer' => [ 'class' => IntegerDef::class ],
51  'limit' => [ 'class' => LimitDef::class ],
52  'namespace' => [
53  'class' => NamespaceDef::class,
54  'services' => [ 'NamespaceInfo' ],
55  ],
56  'NULL' => [
57  'class' => StringDef::class,
58  'args' => [ [
59  'allowEmptyWhenRequired' => true,
60  ] ],
61  ],
62  'password' => [ 'class' => PasswordDef::class ],
63  'string' => [ 'class' => StringDef::class ],
64  'submodule' => [ 'class' => SubmoduleDef::class ],
65  'tags' => [ 'class' => TagsDef::class ],
66  'text' => [ 'class' => StringDef::class ],
67  'timestamp' => [
68  'class' => TimestampDef::class,
69  'args' => [ [
70  'defaultFormat' => TS_MW,
71  ] ],
72  ],
73  'user' => [
74  'class' => UserDef::class,
75  'services' => [ 'UserFactory', 'TitleFactory', 'UserNameUtils' ]
76  ],
77  'upload' => [ 'class' => UploadDef::class ],
78  ];
79 
85  public function __construct( ApiMain $main, ObjectFactory $objectFactory ) {
86  $this->paramValidator = new ParamValidator(
87  new ApiParamValidatorCallbacks( $main ),
88  $objectFactory,
89  [
90  'typeDefs' => self::TYPE_DEFS,
91  'ismultiLimits' => [ ApiBase::LIMIT_SML1, ApiBase::LIMIT_SML2 ],
92  ]
93  );
94  $this->messageConverter = new MessageConverter();
95  }
96 
101  public function knownTypes() : array {
102  return $this->paramValidator->knownTypes();
103  }
104 
110  private function mapDeprecatedSettingsMessages( array $settings ) : array {
111  if ( isset( $settings[EnumDef::PARAM_DEPRECATED_VALUES] ) ) {
112  foreach ( $settings[EnumDef::PARAM_DEPRECATED_VALUES] as &$v ) {
113  if ( $v === null || $v === true || $v instanceof MessageValue ) {
114  continue;
115  }
116 
117  // Convert the message specification to a DataMessageValue. Flag in the data
118  // that it was so converted, so ApiParamValidatorCallbacks::recordCondition() can
119  // take that into account.
120  // @phan-suppress-next-line PhanTypeMismatchArgument
121  $msg = $this->messageConverter->convertMessage( ApiMessage::create( $v ) );
123  $msg->getKey(),
124  $msg->getParams(),
125  'bogus',
126  [ '💩' => 'back-compat' ]
127  );
128  }
129  unset( $v );
130  }
131 
132  return $settings;
133  }
134 
140  public function normalizeSettings( $settings ) : array {
141  if ( is_array( $settings ) ) {
142  if ( !isset( $settings[ParamValidator::PARAM_IGNORE_UNRECOGNIZED_VALUES] ) ) {
144  }
145 
146  if ( !isset( $settings[IntegerDef::PARAM_IGNORE_RANGE] ) ) {
147  $settings[IntegerDef::PARAM_IGNORE_RANGE] = empty( $settings[ApiBase::PARAM_RANGE_ENFORCE] );
148  }
149 
150  $settings = $this->mapDeprecatedSettingsMessages( $settings );
151  }
152 
153  return $this->paramValidator->normalizeSettings( $settings );
154  }
155 
163  private function checkSettingsMessage( ApiBase $module, string $key, $value, array &$ret ) : void {
164  $msg = ApiBase::makeMessage( $value, $module );
165  if ( $msg instanceof Message ) {
166  $ret['messages'][] = $this->messageConverter->convertMessage( $msg );
167  } else {
168  $ret['issues'][] = "Message specification for $key is not valid";
169  }
170  }
171 
180  public function checkSettings(
181  ApiBase $module, array $params, string $name, array $options
182  ) : array {
183  $options['module'] = $module;
184  $settings = $params[$name];
185  if ( is_array( $settings ) ) {
186  $settings = $this->mapDeprecatedSettingsMessages( $settings );
187  }
188  $ret = $this->paramValidator->checkSettings(
189  $module->encodeParamName( $name ), $settings, $options
190  );
191 
192  $ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [
195  ] );
196 
197  if ( !is_array( $settings ) ) {
198  $settings = [];
199  }
200 
201  if ( array_key_exists( ApiBase::PARAM_VALUE_LINKS, $settings ) ) {
202  $ret['issues'][ApiBase::PARAM_VALUE_LINKS]
203  = 'PARAM_VALUE_LINKS was deprecated in MediaWiki 1.35';
204  }
205 
206  if ( !is_bool( $settings[ApiBase::PARAM_RANGE_ENFORCE] ?? false ) ) {
207  $ret['issues'][ApiBase::PARAM_RANGE_ENFORCE] = 'PARAM_RANGE_ENFORCE must be boolean, got '
208  . gettype( $settings[ApiBase::PARAM_RANGE_ENFORCE] );
209  }
210 
211  if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
212  $this->checkSettingsMessage(
213  $module, 'PARAM_HELP_MSG', $settings[ApiBase::PARAM_HELP_MSG], $ret
214  );
215  }
216 
217  if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
218  if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
219  $ret['issues'][ApiBase::PARAM_HELP_MSG_APPEND] = 'PARAM_HELP_MSG_APPEND must be an array, got '
220  . gettype( $settings[ApiBase::PARAM_HELP_MSG_APPEND] );
221  } else {
222  foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $k => $v ) {
223  $this->checkSettingsMessage( $module, "PARAM_HELP_MSG_APPEND[$k]", $v, $ret );
224  }
225  }
226  }
227 
228  if ( isset( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
229  if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
230  $ret['issues'][ApiBase::PARAM_HELP_MSG_INFO] = 'PARAM_HELP_MSG_INFO must be an array, got '
231  . gettype( $settings[ApiBase::PARAM_HELP_MSG_INFO] );
232  } else {
233  $path = $module->getModulePath();
234  foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $k => $v ) {
235  if ( !is_array( $v ) ) {
236  $ret['issues'][] = "PARAM_HELP_MSG_INFO[$k] must be an array, got " . gettype( $v );
237  } elseif ( !is_string( $v[0] ) ) {
238  $ret['issues'][] = "PARAM_HELP_MSG_INFO[$k][0] must be a string, got " . gettype( $v[0] );
239  } else {
240  $v[0] = "apihelp-{$path}-paraminfo-{$v[0]}";
241  $this->checkSettingsMessage( $module, "PARAM_HELP_MSG_INFO[$k]", $v, $ret );
242  }
243  }
244  }
245  }
246 
247  if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
248  if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
249  $ret['issues'][ApiBase::PARAM_HELP_MSG_PER_VALUE] = 'PARAM_HELP_MSG_PER_VALUE must be an array,'
250  . ' got ' . gettype( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] );
251  } elseif ( !is_array( $settings[ParamValidator::PARAM_TYPE] ?? '' ) ) {
252  $ret['issues'][ApiBase::PARAM_HELP_MSG_PER_VALUE] = 'PARAM_HELP_MSG_PER_VALUE can only be used '
253  . 'with PARAM_TYPE as an array';
254  } else {
255  $values = array_map( 'strval', $settings[ParamValidator::PARAM_TYPE] );
256  foreach ( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] as $k => $v ) {
257  if ( !in_array( (string)$k, $values, true ) ) {
258  // Or should this be allowed?
259  $ret['issues'][] = "PARAM_HELP_MSG_PER_VALUE contains \"$k\", which is not in PARAM_TYPE.";
260  }
261  $this->checkSettingsMessage( $module, "PARAM_HELP_MSG_PER_VALUE[$k]", $v, $ret );
262  }
263  }
264  }
265 
266  if ( isset( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ) {
267  if ( !is_array( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ) {
268  $ret['issues'][ApiBase::PARAM_TEMPLATE_VARS] = 'PARAM_TEMPLATE_VARS must be an array,'
269  . ' got ' . gettype( $settings[ApiBase::PARAM_TEMPLATE_VARS] );
270  } elseif ( $settings[ApiBase::PARAM_TEMPLATE_VARS] === [] ) {
271  $ret['issues'][ApiBase::PARAM_TEMPLATE_VARS] = 'PARAM_TEMPLATE_VARS cannot be the empty array';
272  } else {
273  foreach ( $settings[ApiBase::PARAM_TEMPLATE_VARS] as $key => $target ) {
274  if ( !preg_match( '/^[^{}]+$/', $key ) ) {
275  $ret['issues'][] = "PARAM_TEMPLATE_VARS keys may not contain '{' or '}', got \"$key\"";
276  } elseif ( strpos( $name, '{' . $key . '}' ) === false ) {
277  $ret['issues'][] = "Parameter name must contain PARAM_TEMPLATE_VARS key {{$key}}";
278  }
279  if ( !is_string( $target ) && !is_int( $target ) ) {
280  $ret['issues'][] = "PARAM_TEMPLATE_VARS[$key] has invalid target type " . gettype( $target );
281  } elseif ( !isset( $params[$target] ) ) {
282  $ret['issues'][] = "PARAM_TEMPLATE_VARS[$key] target parameter \"$target\" does not exist";
283  } else {
284  $settings2 = $params[$target];
285  if ( empty( $settings2[ParamValidator::PARAM_ISMULTI] ) ) {
286  $ret['issues'][] = "PARAM_TEMPLATE_VARS[$key] target parameter \"$target\" must have "
287  . 'PARAM_ISMULTI = true';
288  }
289  if ( isset( $settings2[ApiBase::PARAM_TEMPLATE_VARS] ) ) {
290  if ( $target === $name ) {
291  $ret['issues'][] = "PARAM_TEMPLATE_VARS[$key] cannot target the parameter itself";
292  }
293  if ( array_diff(
294  $settings2[ApiBase::PARAM_TEMPLATE_VARS],
296  ) ) {
297  $ret['issues'][] = "PARAM_TEMPLATE_VARS[$key]: Target's "
298  . 'PARAM_TEMPLATE_VARS must be a subset of the original';
299  }
300  }
301  }
302  }
303 
304  $keys = implode( '|', array_map(
305  function ( $key ) {
306  return preg_quote( $key, '/' );
307  },
308  array_keys( $settings[ApiBase::PARAM_TEMPLATE_VARS] )
309  ) );
310  if ( !preg_match( '/^(?>[^{}]+|\{(?:' . $keys . ')\})+$/', $name ) ) {
311  $ret['issues'][] = "Parameter name may not contain '{' or '}' other than '
312  . 'as defined by PARAM_TEMPLATE_VARS";
313  }
314  }
315  } elseif ( !preg_match( '/^[^{}]+$/', $name ) ) {
316  $ret['issues'][] = "Parameter name may not contain '{' or '}' without PARAM_TEMPLATE_VARS";
317  }
318 
319  return $ret;
320  }
321 
328  private function convertValidationException( ApiBase $module, ValidationException $ex ) : array {
329  $mv = $ex->getFailureMessage();
331  $module,
332  $this->messageConverter->convertMessageValue( $mv ),
333  $mv->getCode(),
334  $mv->getData(),
335  0,
336  $ex
337  );
338  }
339 
350  public function getValue( ApiBase $module, string $name, $settings, array $options = [] ) {
351  $options['module'] = $module;
352  $name = $module->encodeParamName( $name );
353  $settings = $this->normalizeSettings( $settings );
354  try {
355  return $this->paramValidator->getValue( $name, $settings, $options );
356  } catch ( ValidationException $ex ) {
357  $this->convertValidationException( $module, $ex );
358  }
359  }
360 
373  public function validateValue(
374  ApiBase $module, string $name, $value, $settings, array $options = []
375  ) {
376  $options['module'] = $module;
377  $name = $module->encodeParamName( $name );
378  $settings = $this->normalizeSettings( $settings );
379  try {
380  return $this->paramValidator->validateValue( $name, $value, $settings, $options );
381  } catch ( ValidationException $ex ) {
382  $this->convertValidationException( $module, $ex );
383  }
384  }
385 
396  public function getParamInfo( ApiBase $module, string $name, $settings, array $options ) : array {
397  $options['module'] = $module;
398  $name = $module->encodeParamName( $name );
399  return $this->paramValidator->getParamInfo( $name, $settings, $options );
400  }
401 
412  public function getHelpInfo( ApiBase $module, string $name, $settings, array $options ) : array {
413  $options['module'] = $module;
414  $name = $module->encodeParamName( $name );
415 
416  $ret = $this->paramValidator->getHelpInfo( $name, $settings, $options );
417  foreach ( $ret as &$m ) {
418  $k = $m->getKey();
419  $m = $this->messageConverter->convertMessageValue( $m );
420  if ( substr( $k, 0, 20 ) === 'paramvalidator-help-' ) {
421  $m = new Message(
422  [ 'api-help-param-' . substr( $k, 20 ), $k ],
423  $m->getParams()
424  );
425  }
426  }
427  '@phan-var Message[] $ret'; // The above loop converts it
428 
429  return $ret;
430  }
431 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:48
MediaWiki\Api\Validator\ApiParamValidator\$messageConverter
MessageConverter $messageConverter
Definition: ApiParamValidator.php:43
MediaWiki\Api\Validator\ApiParamValidator\$paramValidator
ParamValidator $paramValidator
Definition: ApiParamValidator.php:40
ApiUsageException
Exception used to abort API execution with an error.
Definition: ApiUsageException.php:29
ApiUsageException\newWithMessage
static newWithMessage(?ApiBase $module, $msg, $code=null, $data=null, $httpCode=0, Throwable $previous=null)
Definition: ApiUsageException.php:68
Wikimedia\ParamValidator\ValidationException
Error reporting for ParamValidator.
Definition: ValidationException.php:16
MediaWiki\Api\Validator\ApiParamValidator\TYPE_DEFS
const TYPE_DEFS
Type defs for ParamValidator.
Definition: ApiParamValidator.php:46
MediaWiki\Api\Validator\ApiParamValidator\getHelpInfo
getHelpInfo(ApiBase $module, string $name, $settings, array $options)
Describe parameter settings in human-readable format.
Definition: ApiParamValidator.php:412
ApiBase\PARAM_HELP_MSG
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:106
MediaWiki\Api\Validator\ApiParamValidator\mapDeprecatedSettingsMessages
mapDeprecatedSettingsMessages(array $settings)
Map deprecated styles for messages for ParamValidator.
Definition: ApiParamValidator.php:110
Wikimedia\ParamValidator\ParamValidator\PARAM_IGNORE_UNRECOGNIZED_VALUES
const PARAM_IGNORE_UNRECOGNIZED_VALUES
(bool) Whether to downgrade "badvalue" errors to non-fatal when validating multi-valued parameters.
Definition: ParamValidator.php:168
MediaWiki\Api\Validator\ApiParamValidator
This wraps a bunch of the API-specific parameter validation logic.
Definition: ApiParamValidator.php:37
Wikimedia\ParamValidator\TypeDef\EnumDef
Type definition for enumeration types.
Definition: EnumDef.php:32
ApiBase\makeMessage
static makeMessage( $msg, IContextSource $context, array $params=null)
Create a Message from a string or array.
Definition: ApiBase.php:1164
ApiBase\PARAM_HELP_MSG_APPEND
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition: ApiBase.php:113
Wikimedia\Message\MessageValue
Value object representing a message for i18n.
Definition: MessageValue.php:16
MediaWiki\Api\Validator\ApiParamValidator\getValue
getValue(ApiBase $module, string $name, $settings, array $options=[])
Get and validate a value.
Definition: ApiParamValidator.php:350
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:52
ApiMessage
Extension of Message implementing IApiMessage @newable.
Definition: ApiMessage.php:27
Wikimedia\ParamValidator\ParamValidator::TypeDef\UserDef
Type definition for user types.
Definition: UserDef.php:25
MediaWiki\Api\Validator\ApiParamValidator\getParamInfo
getParamInfo(ApiBase $module, string $name, $settings, array $options)
Describe parameter settings in a machine-readable format.
Definition: ApiParamValidator.php:396
MediaWiki\Api\Validator\ApiParamValidator\checkSettingsMessage
checkSettingsMessage(ApiBase $module, string $key, $value, array &$ret)
Check an API settings message.
Definition: ApiParamValidator.php:163
MediaWiki\Api\Validator\ApiParamValidator\checkSettings
checkSettings(ApiBase $module, array $params, string $name, array $options)
Check settings for the Action API.
Definition: ApiParamValidator.php:180
Wikimedia\ParamValidator\TypeDef\ExpiryDef
Type definition for expiry timestamps.
Definition: ExpiryDef.php:17
Wikimedia\ParamValidator\ValidationException\getFailureMessage
getFailureMessage()
Fetch the validation failure message.
Definition: ValidationException.php:63
ApiBase\getModulePath
getModulePath()
Get the path to this module.
Definition: ApiBase.php:508
ApiMessage\create
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:42
Wikimedia\ParamValidator\TypeDef\StringDef
Type definition for string types.
Definition: StringDef.php:24
Message\Converter
Converter between Message and MessageValue.
Definition: Converter.php:18
Wikimedia\ParamValidator\TypeDef\TimestampDef
Type definition for timestamp types.
Definition: TimestampDef.php:32
Wikimedia\ParamValidator\ParamValidator\PARAM_ISMULTI
const PARAM_ISMULTI
(bool) Indicate that the parameter is multi-valued.
Definition: ParamValidator.php:112
ApiBase\LIMIT_SML2
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition: ApiBase.php:170
ApiBase\encodeParamName
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:691
Wikimedia\Message\DataMessageValue
Value object representing a message for i18n with alternative machine-readable data.
Definition: DataMessageValue.php:23
MediaWiki\Api\Validator\ApiParamValidatorCallbacks
ParamValidator callbacks for the Action API.
Definition: ApiParamValidatorCallbacks.php:16
MediaWiki\Api\Validator\ApiParamValidator\normalizeSettings
normalizeSettings( $settings)
Adjust certain settings where ParamValidator differs from historical Action API behavior.
Definition: ApiParamValidator.php:140
ApiBase\PARAM_HELP_MSG_INFO
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition: ApiBase.php:123
Wikimedia\Message\DataMessageValue\new
static new( $key, $params=[], $code=null, array $data=null)
Static constructor for easier chaining of ->params() methods.
Definition: DataMessageValue.php:55
Wikimedia\ParamValidator\TypeDef\PasswordDef
Type definition for "password" types.
Definition: PasswordDef.php:16
MediaWiki\Api\Validator
Definition: ApiParamValidator.php:3
ApiBase\PARAM_RANGE_ENFORCE
const PARAM_RANGE_ENFORCE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:92
ApiBase\PARAM_VALUE_LINKS
const PARAM_VALUE_LINKS
Deprecated and unused.
Definition: ApiBase.php:130
Wikimedia\ParamValidator\TypeDef\PresenceBooleanDef
Type definition for checkbox-like boolean types.
Definition: PresenceBooleanDef.php:21
Wikimedia\ParamValidator\TypeDef\LimitDef
Type definition for "limit" types.
Definition: LimitDef.php:18
Wikimedia\ParamValidator\TypeDef\IntegerDef
Type definition for integer types.
Definition: IntegerDef.php:23
ApiBase\PARAM_TEMPLATE_VARS
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:157
Wikimedia\ParamValidator\ParamValidator::TypeDef\NamespaceDef
Type definition for namespace types.
Definition: NamespaceDef.php:18
MediaWiki\Api\Validator\ApiParamValidator\knownTypes
knownTypes()
List known type names.
Definition: ApiParamValidator.php:101
Wikimedia\ParamValidator\TypeDef\EnumDef\PARAM_DEPRECATED_VALUES
const PARAM_DEPRECATED_VALUES
(array) Associative array of deprecated values.
Definition: EnumDef.php:49
Wikimedia\ParamValidator\TypeDef\UploadDef
Type definition for upload types.
Definition: UploadDef.php:34
$path
$path
Definition: NoLocalSettings.php:25
Message
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:161
MediaWiki\Api\Validator\ApiParamValidator\convertValidationException
convertValidationException(ApiBase $module, ValidationException $ex)
Convert a ValidationException to an ApiUsageException.
Definition: ApiParamValidator.php:328
$keys
$keys
Definition: testCompression.php:72
Wikimedia\ParamValidator\TypeDef\NumericDef\PARAM_IGNORE_RANGE
const PARAM_IGNORE_RANGE
(bool) Whether to enforce the specified range.
Definition: NumericDef.php:33
MediaWiki\Api\Validator\ApiParamValidator\__construct
__construct(ApiMain $main, ObjectFactory $objectFactory)
Definition: ApiParamValidator.php:85
Wikimedia\ParamValidator\ParamValidator::TypeDef\TagsDef
Type definition for tags type.
Definition: TagsDef.php:23
ApiBase\PARAM_HELP_MSG_PER_VALUE
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:139
MediaWiki\Api\Validator\ApiParamValidator\validateValue
validateValue(ApiBase $module, string $name, $value, $settings, array $options=[])
Valiate a parameter value using a settings array.
Definition: ApiParamValidator.php:373
Wikimedia\ParamValidator\ParamValidator\PARAM_TYPE
const PARAM_TYPE
(string|array) Type of the parameter.
Definition: ParamValidator.php:76
Wikimedia\ParamValidator\ParamValidator
Service for formatting and validating API parameters.
Definition: ParamValidator.php:42
ApiBase\LIMIT_SML1
const LIMIT_SML1
Slow query, standard limit.
Definition: ApiBase.php:168