Completed
Branch master (62f6c6)
by
unknown
21:31
created

ApiBase   F

Complexity

Total Complexity 378

Size/Duplication

Total Lines 2899
Duplicated Lines 3.9 %

Coupling/Cohesion

Components 2
Dependencies 27

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 113
loc 2899
rs 0.5217
wmc 378
lcom 2
cbo 27

87 Methods

Rating   Name   Duplication   Size   Complexity  
A lacksSameOriginSecurity() 0 9 2
A __construct() 0 9 2
execute() 0 1 ?
A getModuleManager() 0 3 1
A getCustomPrinter() 0 3 1
C getExamplesMessages() 0 41 11
A getHelpUrls() 0 3 1
A getAllowedParams() 0 5 1
A shouldCheckMaxlag() 0 3 1
A isReadMode() 0 3 1
A isWriteMode() 0 3 1
A mustBePosted() 0 3 1
A isDeprecated() 0 3 1
A isInternal() 0 3 1
A needsToken() 0 3 1
A getWebUITokenSalt() 0 3 1
A getConditionalRequestData() 0 3 1
A getModuleName() 0 3 1
A getModulePrefix() 0 3 1
A getMain() 0 3 1
A isMain() 0 3 1
A getParent() 0 3 2
A getModulePath() 0 9 3
C getModuleFromPath() 0 33 7
A getResult() 0 9 2
A getErrorFormatter() 0 9 2
A getDB() 0 7 2
A getContinuationManager() 0 9 2
A setContinuationManager() 0 9 2
A dynamicParameterDocumentation() 0 3 1
A encodeParamName() 0 3 1
A extractRequestParams() 0 17 4
A getParameter() 0 5 1
A requireOnlyOneParameter() 0 19 3
A requireMaxOneParameter() 15 15 2
A requireAtLeastOneParameter() 15 15 2
A parameterNotEmpty() 0 3 2
D getTitleOrPageId() 0 28 9
D getWatchlistValue() 0 32 9
F getParameterFromSettings() 36 235 72
D parseMultiValue() 0 53 15
D validateLimit() 11 32 9
B validateTimestamp() 0 29 4
B validateToken() 0 28 5
A validateUser() 0 11 3
A setWatch() 0 8 2
A truncateArray() 0 9 2
D getWatchlistUser() 0 25 9
A escapeWikiText() 11 11 2
B makeMessage() 0 17 5
A setWarning() 0 4 1
A warnOrDie() 0 7 2
A dieUsage() 0 8 1
A dieBlocked() 0 18 2
C getErrorFromStatus() 0 36 7
A dieStatus() 0 5 1
A dieReadOnly() 0 5 1
A dieUsageMsg() 0 10 3
A dieUsageMsgOrDebug() 0 11 3
A dieContinueUsageIf() 0 7 2
B parseMsg() 0 29 4
A dieDebug() 0 3 1
A logFeatureUsage() 0 9 1
A getDescriptionMessage() 0 3 1
B getFinalDescription() 0 24 3
A getFinalParams() 0 21 4
F getFinalParamDescription() 0 104 20
B getHelpFlags() 0 21 6
C getModuleSourceInfo() 0 75 18
A modifyHelp() 0 2 1
A getDescription() 0 3 1
A getParamDescription() 0 3 1
A getExamples() 0 3 1
D makeHelpMsg() 5 64 16
A indentExampleText() 0 3 1
B makeHelpArrayToString() 0 22 6
F makeHelpMsgParameters() 20 153 43
A getModuleProfileName() 0 4 1
A profileIn() 0 4 1
A profileOut() 0 4 1
A safeProfileOut() 0 3 1
A getProfileTime() 0 4 1
A profileDBIn() 0 3 1
A profileDBOut() 0 3 1
A getProfileDBTime() 0 4 1
A getResultData() 0 4 1
A useTransactionalTimeLimit() 0 5 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ApiBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ApiBase, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 *
4
 *
5
 * Created on Sep 5, 2006
6
 *
7
 * Copyright © 2006, 2010 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License along
20
 * with this program; if not, write to the Free Software Foundation, Inc.,
21
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
 * http://www.gnu.org/copyleft/gpl.html
23
 *
24
 * @file
25
 */
26
27
/**
28
 * This abstract class implements many basic API functions, and is the base of
29
 * all API classes.
30
 * The class functions are divided into several areas of functionality:
31
 *
32
 * Module parameters: Derived classes can define getAllowedParams() to specify
33
 *    which parameters to expect, how to parse and validate them.
34
 *
35
 * Self-documentation: code to allow the API to document its own state
36
 *
37
 * @ingroup API
38
 */
39
abstract class ApiBase extends ContextSource {
40
41
	/**
42
	 * @name Constants for ::getAllowedParams() arrays
43
	 * These constants are keys in the arrays returned by ::getAllowedParams()
44
	 * and accepted by ::getParameterFromSettings() that define how the
45
	 * parameters coming in from the request are to be interpreted.
46
	 * @{
47
	 */
48
49
	/** (null|boolean|integer|string) Default value of the parameter. */
50
	const PARAM_DFLT = 0;
51
52
	/** (boolean) Accept multiple pipe-separated values for this parameter (e.g. titles)? */
53
	const PARAM_ISMULTI = 1;
54
55
	/**
56
	 * (string|string[]) Either an array of allowed value strings, or a string
57
	 * type as described below. If not specified, will be determined from the
58
	 * type of PARAM_DFLT.
59
	 *
60
	 * Supported string types are:
61
	 * - boolean: A boolean parameter, returned as false if the parameter is
62
	 *   omitted and true if present (even with a falsey value, i.e. it works
63
	 *   like HTML checkboxes). PARAM_DFLT must be boolean false, if specified.
64
	 *   Cannot be used with PARAM_ISMULTI.
65
	 * - integer: An integer value. See also PARAM_MIN, PARAM_MAX, and
66
	 *   PARAM_RANGE_ENFORCE.
67
	 * - limit: An integer or the string 'max'. Default lower limit is 0 (but
68
	 *   see PARAM_MIN), and requires that PARAM_MAX and PARAM_MAX2 be
69
	 *   specified. Cannot be used with PARAM_ISMULTI.
70
	 * - namespace: An integer representing a MediaWiki namespace.
71
	 * - NULL: Any string.
72
	 * - password: Any non-empty string. Input value is private or sensitive.
73
	 *   <input type="password"> would be an appropriate HTML form field.
74
	 * - string: Any non-empty string, not expected to be very long or contain newlines.
75
	 *   <input type="text"> would be an appropriate HTML form field.
76
	 * - submodule: The name of a submodule of this module, see PARAM_SUBMODULE_MAP.
77
	 * - tags: A string naming an existing, explicitly-defined tag. Should usually be
78
	 *   used with PARAM_ISMULTI.
79
	 * - text: Any non-empty string, expected to be very long or contain newlines.
80
	 *   <textarea> would be an appropriate HTML form field.
81
	 * - timestamp: A timestamp in any format recognized by MWTimestamp, or the
82
	 *   string 'now' representing the current timestamp. Will be returned in
83
	 *   TS_MW format.
84
	 * - user: A MediaWiki username or IP. Will be returned normalized but not canonicalized.
85
	 * - upload: An uploaded file. Will be returned as a WebRequestUpload object.
86
	 *   Cannot be used with PARAM_ISMULTI.
87
	 */
88
	const PARAM_TYPE = 2;
89
90
	/** (integer) Max value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'. */
91
	const PARAM_MAX = 3;
92
93
	/**
94
	 * (integer) Max value allowed for the parameter for users with the
95
	 * apihighlimits right, for PARAM_TYPE 'limit'.
96
	 */
97
	const PARAM_MAX2 = 4;
98
99
	/** (integer) Lowest value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'. */
100
	const PARAM_MIN = 5;
101
102
	/** (boolean) Allow the same value to be set more than once when PARAM_ISMULTI is true? */
103
	const PARAM_ALLOW_DUPLICATES = 6;
104
105
	/** (boolean) Is the parameter deprecated (will show a warning)? */
106
	const PARAM_DEPRECATED = 7;
107
108
	/**
109
	 * (boolean) Is the parameter required?
110
	 * @since 1.17
111
	 */
112
	const PARAM_REQUIRED = 8;
113
114
	/**
115
	 * (boolean) For PARAM_TYPE 'integer', enforce PARAM_MIN and PARAM_MAX?
116
	 * @since 1.17
117
	 */
118
	const PARAM_RANGE_ENFORCE = 9;
119
120
	/**
121
	 * (string|array|Message) Specify an alternative i18n documentation message
122
	 * for this parameter. Default is apihelp-{$path}-param-{$param}.
123
	 * @since 1.25
124
	 */
125
	const PARAM_HELP_MSG = 10;
126
127
	/**
128
	 * ((string|array|Message)[]) Specify additional i18n messages to append to
129
	 * the normal message for this parameter.
130
	 * @since 1.25
131
	 */
132
	const PARAM_HELP_MSG_APPEND = 11;
133
134
	/**
135
	 * (array) Specify additional information tags for the parameter. Value is
136
	 * an array of arrays, with the first member being the 'tag' for the info
137
	 * and the remaining members being the values. In the help, this is
138
	 * formatted using apihelp-{$path}-paraminfo-{$tag}, which is passed
139
	 * $1 = count, $2 = comma-joined list of values, $3 = module prefix.
140
	 * @since 1.25
141
	 */
142
	const PARAM_HELP_MSG_INFO = 12;
143
144
	/**
145
	 * (string[]) When PARAM_TYPE is an array, this may be an array mapping
146
	 * those values to page titles which will be linked in the help.
147
	 * @since 1.25
148
	 */
149
	const PARAM_VALUE_LINKS = 13;
150
151
	/**
152
	 * ((string|array|Message)[]) When PARAM_TYPE is an array, this is an array
153
	 * mapping those values to $msg for ApiBase::makeMessage(). Any value not
154
	 * having a mapping will use apihelp-{$path}-paramvalue-{$param}-{$value}.
155
	 * @since 1.25
156
	 */
157
	const PARAM_HELP_MSG_PER_VALUE = 14;
158
159
	/**
160
	 * (string[]) When PARAM_TYPE is 'submodule', map parameter values to
161
	 * submodule paths. Default is to use all modules in
162
	 * $this->getModuleManager() in the group matching the parameter name.
163
	 * @since 1.26
164
	 */
165
	const PARAM_SUBMODULE_MAP = 15;
166
167
	/**
168
	 * (string) When PARAM_TYPE is 'submodule', used to indicate the 'g' prefix
169
	 * added by ApiQueryGeneratorBase (and similar if anything else ever does that).
170
	 * @since 1.26
171
	 */
172
	const PARAM_SUBMODULE_PARAM_PREFIX = 16;
173
174
	/**@}*/
175
176
	/** Fast query, standard limit. */
177
	const LIMIT_BIG1 = 500;
178
	/** Fast query, apihighlimits limit. */
179
	const LIMIT_BIG2 = 5000;
180
	/** Slow query, standard limit. */
181
	const LIMIT_SML1 = 50;
182
	/** Slow query, apihighlimits limit. */
183
	const LIMIT_SML2 = 500;
184
185
	/**
186
	 * getAllowedParams() flag: When set, the result could take longer to generate,
187
	 * but should be more thorough. E.g. get the list of generators for ApiSandBox extension
188
	 * @since 1.21
189
	 */
190
	const GET_VALUES_FOR_HELP = 1;
191
192
	/** @var array Maps extension paths to info arrays */
193
	private static $extensionInfo = null;
194
195
	/** @var ApiMain */
196
	private $mMainModule;
197
	/** @var string */
198
	private $mModuleName, $mModulePrefix;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
199
	private $mSlaveDB = null;
200
	private $mParamCache = [];
201
	/** @var array|null|bool */
202
	private $mModuleSource = false;
203
204
	/**
205
	 * @param ApiMain $mainModule
206
	 * @param string $moduleName Name of this module
207
	 * @param string $modulePrefix Prefix to use for parameter names
208
	 */
209
	public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
210
		$this->mMainModule = $mainModule;
211
		$this->mModuleName = $moduleName;
212
		$this->mModulePrefix = $modulePrefix;
213
214
		if ( !$this->isMain() ) {
215
			$this->setContext( $mainModule->getContext() );
216
		}
217
	}
218
219
	/************************************************************************//**
220
	 * @name   Methods to implement
221
	 * @{
222
	 */
223
224
	/**
225
	 * Evaluates the parameters, performs the requested query, and sets up
226
	 * the result. Concrete implementations of ApiBase must override this
227
	 * method to provide whatever functionality their module offers.
228
	 * Implementations must not produce any output on their own and are not
229
	 * expected to handle any errors.
230
	 *
231
	 * The execute() method will be invoked directly by ApiMain immediately
232
	 * before the result of the module is output. Aside from the
233
	 * constructor, implementations should assume that no other methods
234
	 * will be called externally on the module before the result is
235
	 * processed.
236
	 *
237
	 * The result data should be stored in the ApiResult object available
238
	 * through getResult().
239
	 */
240
	abstract public function execute();
241
242
	/**
243
	 * Get the module manager, or null if this module has no sub-modules
244
	 * @since 1.21
245
	 * @return ApiModuleManager
246
	 */
247
	public function getModuleManager() {
248
		return null;
249
	}
250
251
	/**
252
	 * If the module may only be used with a certain format module,
253
	 * it should override this method to return an instance of that formatter.
254
	 * A value of null means the default format will be used.
255
	 * @note Do not use this just because you don't want to support non-json
256
	 * formats. This should be used only when there is a fundamental
257
	 * requirement for a specific format.
258
	 * @return mixed Instance of a derived class of ApiFormatBase, or null
259
	 */
260
	public function getCustomPrinter() {
261
		return null;
262
	}
263
264
	/**
265
	 * Returns usage examples for this module.
266
	 *
267
	 * Return value has query strings as keys, with values being either strings
268
	 * (message key), arrays (message key + parameter), or Message objects.
269
	 *
270
	 * Do not call this base class implementation when overriding this method.
271
	 *
272
	 * @since 1.25
273
	 * @return array
274
	 */
275
	protected function getExamplesMessages() {
276
		// Fall back to old non-localised method
277
		$ret = [];
278
279
		$examples = $this->getExamples();
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::getExamples() has been deprecated with message: since 1.25, use getExamplesMessages() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
280
		if ( $examples ) {
281
			if ( !is_array( $examples ) ) {
282
				$examples = [ $examples ];
283
			} elseif ( $examples && ( count( $examples ) & 1 ) == 0 &&
284
				array_keys( $examples ) === range( 0, count( $examples ) - 1 ) &&
285
				!preg_match( '/^\s*api\.php\?/', $examples[0] )
286
			) {
287
				// Fix up the ugly "even numbered elements are description, odd
288
				// numbered elemts are the link" format (see doc for self::getExamples)
289
				$tmp = [];
290
				$examplesCount = count( $examples );
291
				for ( $i = 0; $i < $examplesCount; $i += 2 ) {
292
					$tmp[$examples[$i + 1]] = $examples[$i];
293
				}
294
				$examples = $tmp;
295
			}
296
297
			foreach ( $examples as $k => $v ) {
298
				if ( is_numeric( $k ) ) {
299
					$qs = $v;
300
					$msg = '';
301
				} else {
302
					$qs = $k;
303
					$msg = self::escapeWikiText( $v );
304
					if ( is_array( $msg ) ) {
305
						$msg = implode( ' ', $msg );
306
					}
307
				}
308
309
				$qs = preg_replace( '/^\s*api\.php\?/', '', $qs );
310
				$ret[$qs] = $this->msg( 'api-help-fallback-example', [ $msg ] );
311
			}
312
		}
313
314
		return $ret;
315
	}
316
317
	/**
318
	 * Return links to more detailed help pages about the module.
319
	 * @since 1.25, returning boolean false is deprecated
320
	 * @return string|array
321
	 */
322
	public function getHelpUrls() {
323
		return [];
324
	}
325
326
	/**
327
	 * Returns an array of allowed parameters (parameter name) => (default
328
	 * value) or (parameter name) => (array with PARAM_* constants as keys)
329
	 * Don't call this function directly: use getFinalParams() to allow
330
	 * hooks to modify parameters as needed.
331
	 *
332
	 * Some derived classes may choose to handle an integer $flags parameter
333
	 * in the overriding methods. Callers of this method can pass zero or
334
	 * more OR-ed flags like GET_VALUES_FOR_HELP.
335
	 *
336
	 * @return array
337
	 */
338
	protected function getAllowedParams( /* $flags = 0 */ ) {
339
		// int $flags is not declared because it causes "Strict standards"
340
		// warning. Most derived classes do not implement it.
341
		return [];
342
	}
343
344
	/**
345
	 * Indicates if this module needs maxlag to be checked
346
	 * @return bool
347
	 */
348
	public function shouldCheckMaxlag() {
349
		return true;
350
	}
351
352
	/**
353
	 * Indicates whether this module requires read rights
354
	 * @return bool
355
	 */
356
	public function isReadMode() {
357
		return true;
358
	}
359
360
	/**
361
	 * Indicates whether this module requires write mode
362
	 * @return bool
363
	 */
364
	public function isWriteMode() {
365
		return false;
366
	}
367
368
	/**
369
	 * Indicates whether this module must be called with a POST request
370
	 * @return bool
371
	 */
372
	public function mustBePosted() {
373
		return $this->needsToken() !== false;
374
	}
375
376
	/**
377
	 * Indicates whether this module is deprecated
378
	 * @since 1.25
379
	 * @return bool
380
	 */
381
	public function isDeprecated() {
382
		return false;
383
	}
384
385
	/**
386
	 * Indicates whether this module is "internal"
387
	 * Internal API modules are not (yet) intended for 3rd party use and may be unstable.
388
	 * @since 1.25
389
	 * @return bool
390
	 */
391
	public function isInternal() {
392
		return false;
393
	}
394
395
	/**
396
	 * Returns the token type this module requires in order to execute.
397
	 *
398
	 * Modules are strongly encouraged to use the core 'csrf' type unless they
399
	 * have specialized security needs. If the token type is not one of the
400
	 * core types, you must use the ApiQueryTokensRegisterTypes hook to
401
	 * register it.
402
	 *
403
	 * Returning a non-falsey value here will force the addition of an
404
	 * appropriate 'token' parameter in self::getFinalParams(). Also,
405
	 * self::mustBePosted() must return true when tokens are used.
406
	 *
407
	 * In previous versions of MediaWiki, true was a valid return value.
408
	 * Returning true will generate errors indicating that the API module needs
409
	 * updating.
410
	 *
411
	 * @return string|false
412
	 */
413
	public function needsToken() {
414
		return false;
415
	}
416
417
	/**
418
	 * Fetch the salt used in the Web UI corresponding to this module.
419
	 *
420
	 * Only override this if the Web UI uses a token with a non-constant salt.
421
	 *
422
	 * @since 1.24
423
	 * @param array $params All supplied parameters for the module
424
	 * @return string|array|null
425
	 */
426
	protected function getWebUITokenSalt( array $params ) {
427
		return null;
428
	}
429
430
	/**
431
	 * Returns data for HTTP conditional request mechanisms.
432
	 *
433
	 * @since 1.26
434
	 * @param string $condition Condition being queried:
435
	 *  - last-modified: Return a timestamp representing the maximum of the
436
	 *    last-modified dates for all resources involved in the request. See
437
	 *    RFC 7232 § 2.2 for semantics.
438
	 *  - etag: Return an entity-tag representing the state of all resources involved
439
	 *    in the request. Quotes must be included. See RFC 7232 § 2.3 for semantics.
440
	 * @return string|bool|null As described above, or null if no value is available.
441
	 */
442
	public function getConditionalRequestData( $condition ) {
443
		return null;
444
	}
445
446
	/**@}*/
447
448
	/************************************************************************//**
449
	 * @name   Data access methods
450
	 * @{
451
	 */
452
453
	/**
454
	 * Get the name of the module being executed by this instance
455
	 * @return string
456
	 */
457
	public function getModuleName() {
458
		return $this->mModuleName;
459
	}
460
461
	/**
462
	 * Get parameter prefix (usually two letters or an empty string).
463
	 * @return string
464
	 */
465
	public function getModulePrefix() {
466
		return $this->mModulePrefix;
467
	}
468
469
	/**
470
	 * Get the main module
471
	 * @return ApiMain
472
	 */
473
	public function getMain() {
474
		return $this->mMainModule;
475
	}
476
477
	/**
478
	 * Returns true if this module is the main module ($this === $this->mMainModule),
479
	 * false otherwise.
480
	 * @return bool
481
	 */
482
	public function isMain() {
483
		return $this === $this->mMainModule;
484
	}
485
486
	/**
487
	 * Get the parent of this module
488
	 * @since 1.25
489
	 * @return ApiBase|null
490
	 */
491
	public function getParent() {
492
		return $this->isMain() ? null : $this->getMain();
493
	}
494
495
	/**
496
	 * Returns true if the current request breaks the same-origin policy.
497
	 *
498
	 * For example, json with callbacks.
499
	 *
500
	 * https://en.wikipedia.org/wiki/Same-origin_policy
501
	 *
502
	 * @since 1.25
503
	 * @return bool
504
	 */
505
	public function lacksSameOriginSecurity() {
506
		// Main module has this method overridden
507
		// Safety - avoid infinite loop:
508
		if ( $this->isMain() ) {
509
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module.' );
510
		}
511
512
		return $this->getMain()->lacksSameOriginSecurity();
513
	}
514
515
	/**
516
	 * Get the path to this module
517
	 *
518
	 * @since 1.25
519
	 * @return string
520
	 */
521
	public function getModulePath() {
522
		if ( $this->isMain() ) {
523
			return 'main';
524
		} elseif ( $this->getParent()->isMain() ) {
525
			return $this->getModuleName();
526
		} else {
527
			return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
528
		}
529
	}
530
531
	/**
532
	 * Get a module from its module path
533
	 *
534
	 * @since 1.25
535
	 * @param string $path
536
	 * @return ApiBase|null
537
	 * @throws UsageException
538
	 */
539
	public function getModuleFromPath( $path ) {
540
		$module = $this->getMain();
541
		if ( $path === 'main' ) {
542
			return $module;
543
		}
544
545
		$parts = explode( '+', $path );
546
		if ( count( $parts ) === 1 ) {
547
			// In case the '+' was typed into URL, it resolves as a space
548
			$parts = explode( ' ', $path );
549
		}
550
551
		$count = count( $parts );
552
		for ( $i = 0; $i < $count; $i++ ) {
553
			$parent = $module;
554
			$manager = $parent->getModuleManager();
555
			if ( $manager === null ) {
556
				$errorPath = implode( '+', array_slice( $parts, 0, $i ) );
557
				$this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' );
558
			}
559
			$module = $manager->getModule( $parts[$i] );
560
561
			if ( $module === null ) {
562
				$errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
563
				$this->dieUsage(
564
					"The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"",
565
					'badmodule'
566
				);
567
			}
568
		}
569
570
		return $module;
571
	}
572
573
	/**
574
	 * Get the result object
575
	 * @return ApiResult
576
	 */
577
	public function getResult() {
578
		// Main module has getResult() method overridden
579
		// Safety - avoid infinite loop:
580
		if ( $this->isMain() ) {
581
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
582
		}
583
584
		return $this->getMain()->getResult();
585
	}
586
587
	/**
588
	 * Get the error formatter
589
	 * @return ApiErrorFormatter
590
	 */
591
	public function getErrorFormatter() {
592
		// Main module has getErrorFormatter() method overridden
593
		// Safety - avoid infinite loop:
594
		if ( $this->isMain() ) {
595
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
596
		}
597
598
		return $this->getMain()->getErrorFormatter();
599
	}
600
601
	/**
602
	 * Gets a default slave database connection object
603
	 * @return DatabaseBase
604
	 */
605
	protected function getDB() {
606
		if ( !isset( $this->mSlaveDB ) ) {
607
			$this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
608
		}
609
610
		return $this->mSlaveDB;
611
	}
612
613
	/**
614
	 * Get the continuation manager
615
	 * @return ApiContinuationManager|null
616
	 */
617
	public function getContinuationManager() {
618
		// Main module has getContinuationManager() method overridden
619
		// Safety - avoid infinite loop:
620
		if ( $this->isMain() ) {
621
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
622
		}
623
624
		return $this->getMain()->getContinuationManager();
625
	}
626
627
	/**
628
	 * Set the continuation manager
629
	 * @param ApiContinuationManager|null
630
	 */
631
	public function setContinuationManager( $manager ) {
632
		// Main module has setContinuationManager() method overridden
633
		// Safety - avoid infinite loop:
634
		if ( $this->isMain() ) {
635
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
636
		}
637
638
		$this->getMain()->setContinuationManager( $manager );
639
	}
640
641
	/**@}*/
642
643
	/************************************************************************//**
644
	 * @name   Parameter handling
645
	 * @{
646
	 */
647
648
	/**
649
	 * Indicate if the module supports dynamically-determined parameters that
650
	 * cannot be included in self::getAllowedParams().
651
	 * @return string|array|Message|null Return null if the module does not
652
	 *  support additional dynamic parameters, otherwise return a message
653
	 *  describing them.
654
	 */
655
	public function dynamicParameterDocumentation() {
656
		return null;
657
	}
658
659
	/**
660
	 * This method mangles parameter name based on the prefix supplied to the constructor.
661
	 * Override this method to change parameter name during runtime
662
	 * @param string $paramName Parameter name
663
	 * @return string Prefixed parameter name
664
	 */
665
	public function encodeParamName( $paramName ) {
666
		return $this->mModulePrefix . $paramName;
667
	}
668
669
	/**
670
	 * Using getAllowedParams(), this function makes an array of the values
671
	 * provided by the user, with key being the name of the variable, and
672
	 * value - validated value from user or default. limits will not be
673
	 * parsed if $parseLimit is set to false; use this when the max
674
	 * limit is not definitive yet, e.g. when getting revisions.
675
	 * @param bool $parseLimit True by default
676
	 * @return array
677
	 */
678
	public function extractRequestParams( $parseLimit = true ) {
679
		// Cache parameters, for performance and to avoid bug 24564.
680
		if ( !isset( $this->mParamCache[$parseLimit] ) ) {
681
			$params = $this->getFinalParams();
682
			$results = [];
683
684
			if ( $params ) { // getFinalParams() can return false
685
				foreach ( $params as $paramName => $paramSettings ) {
686
					$results[$paramName] = $this->getParameterFromSettings(
687
						$paramName, $paramSettings, $parseLimit );
688
				}
689
			}
690
			$this->mParamCache[$parseLimit] = $results;
691
		}
692
693
		return $this->mParamCache[$parseLimit];
694
	}
695
696
	/**
697
	 * Get a value for the given parameter
698
	 * @param string $paramName Parameter name
699
	 * @param bool $parseLimit See extractRequestParams()
700
	 * @return mixed Parameter value
701
	 */
702
	protected function getParameter( $paramName, $parseLimit = true ) {
703
		$paramSettings = $this->getFinalParams()[$paramName];
704
705
		return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
706
	}
707
708
	/**
709
	 * Die if none or more than one of a certain set of parameters is set and not false.
710
	 *
711
	 * @param array $params User provided set of parameters, as from $this->extractRequestParams()
712
	 * @param string $required,... Names of parameters of which exactly one must be set
0 ignored issues
show
Documentation introduced by
There is no parameter named $required,.... Did you maybe mean $required?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
713
	 */
714
	public function requireOnlyOneParameter( $params, $required /*...*/ ) {
715
		$required = func_get_args();
716
		array_shift( $required );
717
		$p = $this->getModulePrefix();
718
719
		$intersection = array_intersect( array_keys( array_filter( $params,
720
			[ $this, 'parameterNotEmpty' ] ) ), $required );
721
722
		if ( count( $intersection ) > 1 ) {
723
			$this->dieUsage(
724
				"The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
725
				'invalidparammix' );
726
		} elseif ( count( $intersection ) == 0 ) {
727
			$this->dieUsage(
728
				"One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
729
				'missingparam'
730
			);
731
		}
732
	}
733
734
	/**
735
	 * Die if more than one of a certain set of parameters is set and not false.
736
	 *
737
	 * @param array $params User provided set of parameters, as from $this->extractRequestParams()
738
	 * @param string $required,... Names of parameters of which at most one must be set
0 ignored issues
show
Documentation introduced by
There is no parameter named $required,.... Did you maybe mean $required?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
739
	 */
740 View Code Duplication
	public function requireMaxOneParameter( $params, $required /*...*/ ) {
741
		$required = func_get_args();
742
		array_shift( $required );
743
		$p = $this->getModulePrefix();
744
745
		$intersection = array_intersect( array_keys( array_filter( $params,
746
			[ $this, 'parameterNotEmpty' ] ) ), $required );
747
748
		if ( count( $intersection ) > 1 ) {
749
			$this->dieUsage(
750
				"The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
751
				'invalidparammix'
752
			);
753
		}
754
	}
755
756
	/**
757
	 * Die if none of a certain set of parameters is set and not false.
758
	 *
759
	 * @since 1.23
760
	 * @param array $params User provided set of parameters, as from $this->extractRequestParams()
761
	 * @param string $required,... Names of parameters of which at least one must be set
0 ignored issues
show
Documentation introduced by
There is no parameter named $required,.... Did you maybe mean $required?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
762
	 */
763 View Code Duplication
	public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
764
		$required = func_get_args();
765
		array_shift( $required );
766
		$p = $this->getModulePrefix();
767
768
		$intersection = array_intersect(
769
			array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
770
			$required
771
		);
772
773
		if ( count( $intersection ) == 0 ) {
774
			$this->dieUsage( "At least one of the parameters {$p}" .
775
				implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
776
		}
777
	}
778
779
	/**
780
	 * Callback function used in requireOnlyOneParameter to check whether required parameters are set
781
	 *
782
	 * @param object $x Parameter to check is not null/false
783
	 * @return bool
784
	 */
785
	private function parameterNotEmpty( $x ) {
786
		return !is_null( $x ) && $x !== false;
787
	}
788
789
	/**
790
	 * Get a WikiPage object from a title or pageid param, if possible.
791
	 * Can die, if no param is set or if the title or page id is not valid.
792
	 *
793
	 * @param array $params
794
	 * @param bool|string $load Whether load the object's state from the database:
795
	 *        - false: don't load (if the pageid is given, it will still be loaded)
796
	 *        - 'fromdb': load from a slave database
797
	 *        - 'fromdbmaster': load from the master database
798
	 * @return WikiPage
799
	 */
800
	public function getTitleOrPageId( $params, $load = false ) {
801
		$this->requireOnlyOneParameter( $params, 'title', 'pageid' );
802
803
		$pageObj = null;
804
		if ( isset( $params['title'] ) ) {
805
			$titleObj = Title::newFromText( $params['title'] );
806
			if ( !$titleObj || $titleObj->isExternal() ) {
807
				$this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
808
			}
809
			if ( !$titleObj->canExist() ) {
810
				$this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
811
			}
812
			$pageObj = WikiPage::factory( $titleObj );
0 ignored issues
show
Bug introduced by
It seems like $titleObj defined by \Title::newFromText($params['title']) on line 805 can be null; however, WikiPage::factory() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
813
			if ( $load !== false ) {
814
				$pageObj->loadPageData( $load );
0 ignored issues
show
Bug introduced by
It seems like $load defined by parameter $load on line 800 can also be of type boolean; however, WikiPage::loadPageData() does only seem to accept string|object|integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
815
			}
816
		} elseif ( isset( $params['pageid'] ) ) {
817
			if ( $load === false ) {
818
				$load = 'fromdb';
819
			}
820
			$pageObj = WikiPage::newFromID( $params['pageid'], $load );
0 ignored issues
show
Bug introduced by
It seems like $load defined by parameter $load on line 800 can also be of type boolean; however, WikiPage::newFromID() does only seem to accept string|integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
821
			if ( !$pageObj ) {
822
				$this->dieUsageMsg( [ 'nosuchpageid', $params['pageid'] ] );
823
			}
824
		}
825
826
		return $pageObj;
827
	}
828
829
	/**
830
	 * Return true if we're to watch the page, false if not, null if no change.
831
	 * @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
832
	 * @param Title $titleObj The page under consideration
833
	 * @param string $userOption The user option to consider when $watchlist=preferences.
834
	 *    If not set will use watchdefault always and watchcreations if $titleObj doesn't exist.
835
	 * @return bool
836
	 */
837
	protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
838
839
		$userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
840
841
		switch ( $watchlist ) {
842
			case 'watch':
843
				return true;
844
845
			case 'unwatch':
846
				return false;
847
848
			case 'preferences':
849
				# If the user is already watching, don't bother checking
850
				if ( $userWatching ) {
851
					return true;
852
				}
853
				# If no user option was passed, use watchdefault and watchcreations
854
				if ( is_null( $userOption ) ) {
855
					return $this->getUser()->getBoolOption( 'watchdefault' ) ||
856
						$this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
857
				}
858
859
				# Watch the article based on the user preference
860
				return $this->getUser()->getBoolOption( $userOption );
861
862
			case 'nochange':
863
				return $userWatching;
864
865
			default:
866
				return $userWatching;
867
		}
868
	}
869
870
	/**
871
	 * Using the settings determine the value for the given parameter
872
	 *
873
	 * @param string $paramName Parameter name
874
	 * @param array|mixed $paramSettings Default value or an array of settings
875
	 *  using PARAM_* constants.
876
	 * @param bool $parseLimit Parse limit?
877
	 * @return mixed Parameter value
878
	 */
879
	protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
880
		// Some classes may decide to change parameter names
881
		$encParamName = $this->encodeParamName( $paramName );
882
883
		if ( !is_array( $paramSettings ) ) {
884
			$default = $paramSettings;
885
			$multi = false;
886
			$type = gettype( $paramSettings );
887
			$dupes = false;
888
			$deprecated = false;
889
			$required = false;
890
		} else {
891
			$default = isset( $paramSettings[self::PARAM_DFLT] )
892
				? $paramSettings[self::PARAM_DFLT]
893
				: null;
894
			$multi = isset( $paramSettings[self::PARAM_ISMULTI] )
895
				? $paramSettings[self::PARAM_ISMULTI]
896
				: false;
897
			$type = isset( $paramSettings[self::PARAM_TYPE] )
898
				? $paramSettings[self::PARAM_TYPE]
899
				: null;
900
			$dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
901
				? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
902
				: false;
903
			$deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
904
				? $paramSettings[self::PARAM_DEPRECATED]
905
				: false;
906
			$required = isset( $paramSettings[self::PARAM_REQUIRED] )
907
				? $paramSettings[self::PARAM_REQUIRED]
908
				: false;
909
910
			// When type is not given, and no choices, the type is the same as $default
911
			if ( !isset( $type ) ) {
912
				if ( isset( $default ) ) {
913
					$type = gettype( $default );
914
				} else {
915
					$type = 'NULL'; // allow everything
916
				}
917
			}
918
		}
919
920
		if ( $type == 'boolean' ) {
921
			if ( isset( $default ) && $default !== false ) {
922
				// Having a default value of anything other than 'false' is not allowed
923
				ApiBase::dieDebug(
924
					__METHOD__,
925
					"Boolean param $encParamName's default is set to '$default'. " .
926
						'Boolean parameters must default to false.'
927
				);
928
			}
929
930
			$value = $this->getMain()->getCheck( $encParamName );
931
		} elseif ( $type == 'upload' ) {
932
			if ( isset( $default ) ) {
933
				// Having a default value is not allowed
934
				ApiBase::dieDebug(
935
					__METHOD__,
936
					"File upload param $encParamName's default is set to " .
937
						"'$default'. File upload parameters may not have a default." );
938
			}
939
			if ( $multi ) {
940
				ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
941
			}
942
			$value = $this->getMain()->getUpload( $encParamName );
943
			if ( !$value->exists() ) {
944
				// This will get the value without trying to normalize it
945
				// (because trying to normalize a large binary file
946
				// accidentally uploaded as a field fails spectacularly)
947
				$value = $this->getMain()->getRequest()->unsetVal( $encParamName );
948
				if ( $value !== null ) {
949
					$this->dieUsage(
950
						"File upload param $encParamName is not a file upload; " .
951
							'be sure to use multipart/form-data for your POST and include ' .
952
							'a filename in the Content-Disposition header.',
953
						"badupload_{$encParamName}"
954
					);
955
				}
956
			}
957
		} else {
958
			$value = $this->getMain()->getVal( $encParamName, $default );
959
960
			if ( isset( $value ) && $type == 'namespace' ) {
961
				$type = MWNamespace::getValidNamespaces();
962
			}
963 View Code Duplication
			if ( isset( $value ) && $type == 'submodule' ) {
964
				if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
965
					$type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
966
				} else {
967
					$type = $this->getModuleManager()->getNames( $paramName );
968
				}
969
			}
970
		}
971
972
		if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
973
			$value = $this->parseMultiValue(
974
				$encParamName,
975
				$value,
976
				$multi,
977
				is_array( $type ) ? $type : null
0 ignored issues
show
Bug introduced by
It seems like is_array($type) ? $type : null can also be of type array; however, ApiBase::parseMultiValue() does only seem to accept array<integer,string>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
978
			);
979
		}
980
981
		// More validation only when choices were not given
982
		// choices were validated in parseMultiValue()
983
		if ( isset( $value ) ) {
984
			if ( !is_array( $type ) ) {
985
				switch ( $type ) {
986
					case 'NULL': // nothing to do
987
						break;
988
					case 'string':
989
					case 'text':
990
					case 'password':
991
						if ( $required && $value === '' ) {
992
							$this->dieUsageMsg( [ 'missingparam', $paramName ] );
993
						}
994
						break;
995
					case 'integer': // Force everything using intval() and optionally validate limits
996
						$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
997
						$max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
998
						$enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
999
							? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
1000
1001
						if ( is_array( $value ) ) {
1002
							$value = array_map( 'intval', $value );
1003 View Code Duplication
							if ( !is_null( $min ) || !is_null( $max ) ) {
1004
								foreach ( $value as &$v ) {
1005
									$this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1006
								}
1007
							}
1008 View Code Duplication
						} else {
1009
							$value = intval( $value );
1010
							if ( !is_null( $min ) || !is_null( $max ) ) {
1011
								$this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1012
							}
1013
						}
1014
						break;
1015
					case 'limit':
1016
						if ( !$parseLimit ) {
1017
							// Don't do any validation whatsoever
1018
							break;
1019
						}
1020
						if ( !isset( $paramSettings[self::PARAM_MAX] )
1021
							|| !isset( $paramSettings[self::PARAM_MAX2] )
1022
						) {
1023
							ApiBase::dieDebug(
1024
								__METHOD__,
1025
								"MAX1 or MAX2 are not defined for the limit $encParamName"
1026
							);
1027
						}
1028
						if ( $multi ) {
1029
							ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1030
						}
1031
						$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
1032
						if ( $value == 'max' ) {
1033
							$value = $this->getMain()->canApiHighLimits()
1034
								? $paramSettings[self::PARAM_MAX2]
1035
								: $paramSettings[self::PARAM_MAX];
1036
							$this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1037
						} else {
1038
							$value = intval( $value );
1039
							$this->validateLimit(
1040
								$paramName,
1041
								$value,
1042
								$min,
1043
								$paramSettings[self::PARAM_MAX],
1044
								$paramSettings[self::PARAM_MAX2]
1045
							);
1046
						}
1047
						break;
1048
					case 'boolean':
1049
						if ( $multi ) {
1050
							ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1051
						}
1052
						break;
1053 View Code Duplication
					case 'timestamp':
1054
						if ( is_array( $value ) ) {
1055
							foreach ( $value as $key => $val ) {
1056
								$value[$key] = $this->validateTimestamp( $val, $encParamName );
1057
							}
1058
						} else {
1059
							$value = $this->validateTimestamp( $value, $encParamName );
1060
						}
1061
						break;
1062 View Code Duplication
					case 'user':
1063
						if ( is_array( $value ) ) {
1064
							foreach ( $value as $key => $val ) {
1065
								$value[$key] = $this->validateUser( $val, $encParamName );
1066
							}
1067
						} else {
1068
							$value = $this->validateUser( $value, $encParamName );
1069
						}
1070
						break;
1071
					case 'upload': // nothing to do
1072
						break;
1073
					case 'tags':
1074
						// If change tagging was requested, check that the tags are valid.
1075
						if ( !is_array( $value ) && !$multi ) {
1076
							$value = [ $value ];
1077
						}
1078
						$tagsStatus = ChangeTags::canAddTagsAccompanyingChange( $value );
1079
						if ( !$tagsStatus->isGood() ) {
1080
							$this->dieStatus( $tagsStatus );
1081
						}
1082
						break;
1083
					default:
1084
						ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1085
				}
1086
			}
1087
1088
			// Throw out duplicates if requested
1089
			if ( !$dupes && is_array( $value ) ) {
1090
				$value = array_unique( $value );
1091
			}
1092
1093
			// Set a warning if a deprecated parameter has been passed
1094
			if ( $deprecated && $value !== false ) {
1095
				$this->setWarning( "The $encParamName parameter has been deprecated." );
1096
1097
				$feature = $encParamName;
1098
				$m = $this;
1099
				while ( !$m->isMain() ) {
1100
					$p = $m->getParent();
1101
					$name = $m->getModuleName();
1102
					$param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1103
					$feature = "{$param}={$name}&{$feature}";
1104
					$m = $p;
1105
				}
1106
				$this->logFeatureUsage( $feature );
1107
			}
1108
		} elseif ( $required ) {
1109
			$this->dieUsageMsg( [ 'missingparam', $paramName ] );
1110
		}
1111
1112
		return $value;
1113
	}
1114
1115
	/**
1116
	 * Return an array of values that were given in a 'a|b|c' notation,
1117
	 * after it optionally validates them against the list allowed values.
1118
	 *
1119
	 * @param string $valueName The name of the parameter (for error
1120
	 *  reporting)
1121
	 * @param mixed $value The value being parsed
1122
	 * @param bool $allowMultiple Can $value contain more than one value
1123
	 *  separated by '|'?
1124
	 * @param string[]|null $allowedValues An array of values to check against. If
1125
	 *  null, all values are accepted.
1126
	 * @return string|string[] (allowMultiple ? an_array_of_values : a_single_value)
1127
	 */
1128
	protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
1129
		if ( trim( $value ) === '' && $allowMultiple ) {
1130
			return [];
1131
		}
1132
1133
		// This is a bit awkward, but we want to avoid calling canApiHighLimits()
1134
		// because it unstubs $wgUser
1135
		$valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
1136
		$sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
1137
			? self::LIMIT_SML2
1138
			: self::LIMIT_SML1;
1139
1140
		if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
1141
			$this->setWarning( "Too many values supplied for parameter '$valueName': " .
1142
				"the limit is $sizeLimit" );
1143
		}
1144
1145
		if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1146
			// Bug 33482 - Allow entries with | in them for non-multiple values
1147
			if ( in_array( $value, $allowedValues, true ) ) {
1148
				return $value;
1149
			}
1150
1151
			$possibleValues = is_array( $allowedValues )
1152
				? "of '" . implode( "', '", $allowedValues ) . "'"
1153
				: '';
1154
			$this->dieUsage(
1155
				"Only one $possibleValues is allowed for parameter '$valueName'",
1156
				"multival_$valueName"
1157
			);
1158
		}
1159
1160
		if ( is_array( $allowedValues ) ) {
1161
			// Check for unknown values
1162
			$unknown = array_diff( $valuesList, $allowedValues );
1163
			if ( count( $unknown ) ) {
1164
				if ( $allowMultiple ) {
1165
					$s = count( $unknown ) > 1 ? 's' : '';
1166
					$vals = implode( ', ', $unknown );
1167
					$this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
1168
				} else {
1169
					$this->dieUsage(
1170
						"Unrecognized value for parameter '$valueName': {$valuesList[0]}",
1171
						"unknown_$valueName"
1172
					);
1173
				}
1174
			}
1175
			// Now throw them out
1176
			$valuesList = array_intersect( $valuesList, $allowedValues );
1177
		}
1178
1179
		return $allowMultiple ? $valuesList : $valuesList[0];
1180
	}
1181
1182
	/**
1183
	 * Validate the value against the minimum and user/bot maximum limits.
1184
	 * Prints usage info on failure.
1185
	 * @param string $paramName Parameter name
1186
	 * @param int $value Parameter value
1187
	 * @param int|null $min Minimum value
1188
	 * @param int|null $max Maximum value for users
1189
	 * @param int $botMax Maximum value for sysops/bots
1190
	 * @param bool $enforceLimits Whether to enforce (die) if value is outside limits
1191
	 */
1192
	protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1193
		$enforceLimits = false
1194
	) {
1195
		if ( !is_null( $min ) && $value < $min ) {
1196
			$msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
1197
			$this->warnOrDie( $msg, $enforceLimits );
1198
			$value = $min;
1199
		}
1200
1201
		// Minimum is always validated, whereas maximum is checked only if not
1202
		// running in internal call mode
1203
		if ( $this->getMain()->isInternalMode() ) {
1204
			return;
1205
		}
1206
1207
		// Optimization: do not check user's bot status unless really needed -- skips db query
1208
		// assumes $botMax >= $max
1209
		if ( !is_null( $max ) && $value > $max ) {
1210
			if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1211 View Code Duplication
				if ( $value > $botMax ) {
1212
					$msg = $this->encodeParamName( $paramName ) .
1213
						" may not be over $botMax (set to $value) for bots or sysops";
1214
					$this->warnOrDie( $msg, $enforceLimits );
1215
					$value = $botMax;
1216
				}
1217 View Code Duplication
			} else {
1218
				$msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
1219
				$this->warnOrDie( $msg, $enforceLimits );
1220
				$value = $max;
1221
			}
1222
		}
1223
	}
1224
1225
	/**
1226
	 * Validate and normalize of parameters of type 'timestamp'
1227
	 * @param string $value Parameter value
1228
	 * @param string $encParamName Parameter name
1229
	 * @return string Validated and normalized parameter
1230
	 */
1231
	protected function validateTimestamp( $value, $encParamName ) {
1232
		// Confusing synonyms for the current time accepted by wfTimestamp()
1233
		// (wfTimestamp() also accepts various non-strings and the string of 14
1234
		// ASCII NUL bytes, but those can't get here)
1235
		if ( !$value ) {
1236
			$this->logFeatureUsage( 'unclear-"now"-timestamp' );
1237
			$this->setWarning(
1238
				"Passing '$value' for timestamp parameter $encParamName has been deprecated." .
1239
					' If for some reason you need to explicitly specify the current time without' .
1240
					' calculating it client-side, use "now".'
1241
			);
1242
			return wfTimestamp( TS_MW );
1243
		}
1244
1245
		// Explicit synonym for the current time
1246
		if ( $value === 'now' ) {
1247
			return wfTimestamp( TS_MW );
1248
		}
1249
1250
		$unixTimestamp = wfTimestamp( TS_UNIX, $value );
1251
		if ( $unixTimestamp === false ) {
1252
			$this->dieUsage(
1253
				"Invalid value '$value' for timestamp parameter $encParamName",
1254
				"badtimestamp_{$encParamName}"
1255
			);
1256
		}
1257
1258
		return wfTimestamp( TS_MW, $unixTimestamp );
1259
	}
1260
1261
	/**
1262
	 * Validate the supplied token.
1263
	 *
1264
	 * @since 1.24
1265
	 * @param string $token Supplied token
1266
	 * @param array $params All supplied parameters for the module
1267
	 * @return bool
1268
	 * @throws MWException
1269
	 */
1270
	final public function validateToken( $token, array $params ) {
1271
		$tokenType = $this->needsToken();
1272
		$salts = ApiQueryTokens::getTokenTypeSalts();
1273
		if ( !isset( $salts[$tokenType] ) ) {
1274
			throw new MWException(
1275
				"Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1276
					'without registering it'
1277
			);
1278
		}
1279
1280
		$tokenObj = ApiQueryTokens::getToken(
1281
			$this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1282
		);
1283
		if ( $tokenObj->match( $token ) ) {
1284
			return true;
1285
		}
1286
1287
		$webUiSalt = $this->getWebUITokenSalt( $params );
1288
		if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1289
			$token,
1290
			$webUiSalt,
0 ignored issues
show
Bug introduced by
It seems like $webUiSalt defined by $this->getWebUITokenSalt($params) on line 1287 can also be of type array; however, User::matchEditToken() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1291
			$this->getRequest()
1292
		) ) {
1293
			return true;
1294
		}
1295
1296
		return false;
1297
	}
1298
1299
	/**
1300
	 * Validate and normalize of parameters of type 'user'
1301
	 * @param string $value Parameter value
1302
	 * @param string $encParamName Parameter name
1303
	 * @return string Validated and normalized parameter
1304
	 */
1305
	private function validateUser( $value, $encParamName ) {
1306
		$title = Title::makeTitleSafe( NS_USER, $value );
1307
		if ( $title === null || $title->hasFragment() ) {
1308
			$this->dieUsage(
1309
				"Invalid value '$value' for user parameter $encParamName",
1310
				"baduser_{$encParamName}"
1311
			);
1312
		}
1313
1314
		return $title->getText();
1315
	}
1316
1317
	/**@}*/
1318
1319
	/************************************************************************//**
1320
	 * @name   Utility methods
1321
	 * @{
1322
	 */
1323
1324
	/**
1325
	 * Set a watch (or unwatch) based the based on a watchlist parameter.
1326
	 * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
1327
	 * @param Title $titleObj The article's title to change
1328
	 * @param string $userOption The user option to consider when $watch=preferences
1329
	 */
1330
	protected function setWatch( $watch, $titleObj, $userOption = null ) {
1331
		$value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1332
		if ( $value === null ) {
1333
			return;
1334
		}
1335
1336
		WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1337
	}
1338
1339
	/**
1340
	 * Truncate an array to a certain length.
1341
	 * @param array $arr Array to truncate
1342
	 * @param int $limit Maximum length
1343
	 * @return bool True if the array was truncated, false otherwise
1344
	 */
1345
	public static function truncateArray( &$arr, $limit ) {
1346
		$modified = false;
1347
		while ( count( $arr ) > $limit ) {
1348
			array_pop( $arr );
1349
			$modified = true;
1350
		}
1351
1352
		return $modified;
1353
	}
1354
1355
	/**
1356
	 * Gets the user for whom to get the watchlist
1357
	 *
1358
	 * @param array $params
1359
	 * @return User
1360
	 */
1361
	public function getWatchlistUser( $params ) {
1362
		if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1363
			$user = User::newFromName( $params['owner'], false );
1364
			if ( !( $user && $user->getId() ) ) {
1365
				$this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
1366
			}
1367
			$token = $user->getOption( 'watchlisttoken' );
1368
			if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1369
				$this->dieUsage(
1370
					'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
1371
					'bad_wltoken'
1372
				);
1373
			}
1374
		} else {
1375
			if ( !$this->getUser()->isLoggedIn() ) {
1376
				$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
1377
			}
1378
			if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
1379
				$this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
1380
			}
1381
			$user = $this->getUser();
1382
		}
1383
1384
		return $user;
1385
	}
1386
1387
	/**
1388
	 * A subset of wfEscapeWikiText for BC texts
1389
	 *
1390
	 * @since 1.25
1391
	 * @param string|array $v
1392
	 * @return string|array
1393
	 */
1394 View Code Duplication
	private static function escapeWikiText( $v ) {
1395
		if ( is_array( $v ) ) {
1396
			return array_map( 'self::escapeWikiText', $v );
1397
		} else {
1398
			return strtr( $v, [
1399
				'__' => '_&#95;', '{' => '&#123;', '}' => '&#125;',
1400
				'[[Category:' => '[[:Category:',
1401
				'[[File:' => '[[:File:', '[[Image:' => '[[:Image:',
1402
			] );
1403
		}
1404
	}
1405
1406
	/**
1407
	 * Create a Message from a string or array
1408
	 *
1409
	 * A string is used as a message key. An array has the message key as the
1410
	 * first value and message parameters as subsequent values.
1411
	 *
1412
	 * @since 1.25
1413
	 * @param string|array|Message $msg
1414
	 * @param IContextSource $context
1415
	 * @param array $params
1416
	 * @return Message|null
1417
	 */
1418
	public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1419
		if ( is_string( $msg ) ) {
1420
			$msg = wfMessage( $msg );
1421
		} elseif ( is_array( $msg ) ) {
1422
			$msg = call_user_func_array( 'wfMessage', $msg );
1423
		}
1424
		if ( !$msg instanceof Message ) {
1425
			return null;
1426
		}
1427
1428
		$msg->setContext( $context );
1429
		if ( $params ) {
1430
			$msg->params( $params );
1431
		}
1432
1433
		return $msg;
1434
	}
1435
1436
	/**@}*/
1437
1438
	/************************************************************************//**
1439
	 * @name   Warning and error reporting
1440
	 * @{
1441
	 */
1442
1443
	/**
1444
	 * Set warning section for this module. Users should monitor this
1445
	 * section to notice any changes in API. Multiple calls to this
1446
	 * function will result in the warning messages being separated by
1447
	 * newlines
1448
	 * @param string $warning Warning message
1449
	 */
1450
	public function setWarning( $warning ) {
1451
		$msg = new ApiRawMessage( $warning, 'warning' );
1452
		$this->getErrorFormatter()->addWarning( $this->getModuleName(), $msg );
1453
	}
1454
1455
	/**
1456
	 * Adds a warning to the output, else dies
1457
	 *
1458
	 * @param string $msg Message to show as a warning, or error message if dying
1459
	 * @param bool $enforceLimits Whether this is an enforce (die)
1460
	 */
1461
	private function warnOrDie( $msg, $enforceLimits = false ) {
1462
		if ( $enforceLimits ) {
1463
			$this->dieUsage( $msg, 'integeroutofrange' );
1464
		}
1465
1466
		$this->setWarning( $msg );
1467
	}
1468
1469
	/**
1470
	 * Throw a UsageException, which will (if uncaught) call the main module's
1471
	 * error handler and die with an error message.
1472
	 *
1473
	 * @param string $description One-line human-readable description of the
1474
	 *   error condition, e.g., "The API requires a valid action parameter"
1475
	 * @param string $errorCode Brief, arbitrary, stable string to allow easy
1476
	 *   automated identification of the error, e.g., 'unknown_action'
1477
	 * @param int $httpRespCode HTTP response code
1478
	 * @param array|null $extradata Data to add to the "<error>" element; array in ApiResult format
1479
	 * @throws UsageException always
1480
	 */
1481
	public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
1482
		throw new UsageException(
1483
			$description,
1484
			$this->encodeParamName( $errorCode ),
1485
			$httpRespCode,
1486
			$extradata
1487
		);
1488
	}
1489
1490
	/**
1491
	 * Throw a UsageException, which will (if uncaught) call the main module's
1492
	 * error handler and die with an error message including block info.
1493
	 *
1494
	 * @since 1.27
1495
	 * @param Block $block The block used to generate the UsageException
1496
	 * @throws UsageException always
1497
	 */
1498
	public function dieBlocked( Block $block ) {
1499
		// Die using the appropriate message depending on block type
1500
		if ( $block->getType() == Block::TYPE_AUTO ) {
1501
			$this->dieUsage(
1502
				'Your IP address has been blocked automatically, because it was used by a blocked user',
1503
				'autoblocked',
1504
				0,
1505
				[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1506
			);
1507
		} else {
1508
			$this->dieUsage(
1509
				'You have been blocked from editing',
1510
				'blocked',
1511
				0,
1512
				[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1513
			);
1514
		}
1515
	}
1516
1517
	/**
1518
	 * Get error (as code, string) from a Status object.
1519
	 *
1520
	 * @since 1.23
1521
	 * @param Status $status
1522
	 * @param array|null &$extraData Set if extra data from IApiMessage is available (since 1.27)
1523
	 * @return array Array of code and error string
1524
	 * @throws MWException
1525
	 */
1526
	public function getErrorFromStatus( $status, &$extraData = null ) {
1527
		if ( $status->isGood() ) {
1528
			throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
1529
		}
1530
1531
		$errors = $status->getErrorsByType( 'error' );
1532
		if ( !$errors ) {
1533
			// No errors? Assume the warnings should be treated as errors
1534
			$errors = $status->getErrorsByType( 'warning' );
1535
		}
1536
		if ( !$errors ) {
1537
			// Still no errors? Punt
1538
			$errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
1539
		}
1540
1541
		// Cannot use dieUsageMsg() because extensions might return custom
1542
		// error messages.
1543
		if ( $errors[0]['message'] instanceof Message ) {
1544
			$msg = $errors[0]['message'];
1545
			if ( $msg instanceof IApiMessage ) {
1546
				$extraData = $msg->getApiData();
1547
				$code = $msg->getApiCode();
1548
			} else {
1549
				$code = $msg->getKey();
1550
			}
1551
		} else {
1552
			$code = $errors[0]['message'];
1553
			$msg = wfMessage( $code, $errors[0]['params'] );
1554
		}
1555
		if ( isset( ApiBase::$messageMap[$code] ) ) {
1556
			// Translate message to code, for backwards compatibility
1557
			$code = ApiBase::$messageMap[$code]['code'];
1558
		}
1559
1560
		return [ $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() ];
1561
	}
1562
1563
	/**
1564
	 * Throw a UsageException based on the errors in the Status object.
1565
	 *
1566
	 * @since 1.22
1567
	 * @param Status $status
1568
	 * @throws UsageException always
1569
	 */
1570
	public function dieStatus( $status ) {
1571
		$extraData = null;
1572
		list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
1573
		$this->dieUsage( $msg, $code, 0, $extraData );
1574
	}
1575
1576
	// @codingStandardsIgnoreStart Allow long lines. Cannot split these.
1577
	/**
1578
	 * Array that maps message keys to error messages. $1 and friends are replaced.
1579
	 */
1580
	public static $messageMap = [
1581
		// This one MUST be present, or dieUsageMsg() will recurse infinitely
1582
		'unknownerror' => [ 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ],
1583
		'unknownerror-nocode' => [ 'code' => 'unknownerror', 'info' => 'Unknown error' ],
1584
1585
		// Messages from Title::getUserPermissionsErrors()
1586
		'ns-specialprotected' => [
1587
			'code' => 'unsupportednamespace',
1588
			'info' => "Pages in the Special namespace can't be edited"
1589
		],
1590
		'protectedinterface' => [
1591
			'code' => 'protectednamespace-interface',
1592
			'info' => "You're not allowed to edit interface messages"
1593
		],
1594
		'namespaceprotected' => [
1595
			'code' => 'protectednamespace',
1596
			'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
1597
		],
1598
		'customcssprotected' => [
1599
			'code' => 'customcssprotected',
1600
			'info' => "You're not allowed to edit custom CSS pages"
1601
		],
1602
		'customjsprotected' => [
1603
			'code' => 'customjsprotected',
1604
			'info' => "You're not allowed to edit custom JavaScript pages"
1605
		],
1606
		'cascadeprotected' => [
1607
			'code' => 'cascadeprotected',
1608
			'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
1609
		],
1610
		'protectedpagetext' => [
1611
			'code' => 'protectedpage',
1612
			'info' => "The \"\$1\" right is required to edit this page"
1613
		],
1614
		'protect-cantedit' => [
1615
			'code' => 'cantedit',
1616
			'info' => "You can't protect this page because you can't edit it"
1617
		],
1618
		'deleteprotected' => [
1619
			'code' => 'cantedit',
1620
			'info' => "You can't delete this page because it has been protected"
1621
		],
1622
		'badaccess-group0' => [
1623
			'code' => 'permissiondenied',
1624
			'info' => 'Permission denied'
1625
		], // Generic permission denied message
1626
		'badaccess-groups' => [
1627
			'code' => 'permissiondenied',
1628
			'info' => 'Permission denied'
1629
		],
1630
		'titleprotected' => [
1631
			'code' => 'protectedtitle',
1632
			'info' => 'This title has been protected from creation'
1633
		],
1634
		'nocreate-loggedin' => [
1635
			'code' => 'cantcreate',
1636
			'info' => "You don't have permission to create new pages"
1637
		],
1638
		'nocreatetext' => [
1639
			'code' => 'cantcreate-anon',
1640
			'info' => "Anonymous users can't create new pages"
1641
		],
1642
		'movenologintext' => [
1643
			'code' => 'cantmove-anon',
1644
			'info' => "Anonymous users can't move pages"
1645
		],
1646
		'movenotallowed' => [
1647
			'code' => 'cantmove',
1648
			'info' => "You don't have permission to move pages"
1649
		],
1650
		'confirmedittext' => [
1651
			'code' => 'confirmemail',
1652
			'info' => 'You must confirm your email address before you can edit'
1653
		],
1654
		'blockedtext' => [
1655
			'code' => 'blocked',
1656
			'info' => 'You have been blocked from editing'
1657
		],
1658
		'autoblockedtext' => [
1659
			'code' => 'autoblocked',
1660
			'info' => 'Your IP address has been blocked automatically, because it was used by a blocked user'
1661
		],
1662
1663
		// Miscellaneous interface messages
1664
		'actionthrottledtext' => [
1665
			'code' => 'ratelimited',
1666
			'info' => "You've exceeded your rate limit. Please wait some time and try again"
1667
		],
1668
		'alreadyrolled' => [
1669
			'code' => 'alreadyrolled',
1670
			'info' => 'The page you tried to rollback was already rolled back'
1671
		],
1672
		'cantrollback' => [
1673
			'code' => 'onlyauthor',
1674
			'info' => 'The page you tried to rollback only has one author'
1675
		],
1676
		'readonlytext' => [
1677
			'code' => 'readonly',
1678
			'info' => 'The wiki is currently in read-only mode'
1679
		],
1680
		'sessionfailure' => [
1681
			'code' => 'badtoken',
1682
			'info' => 'Invalid token' ],
1683
		'cannotdelete' => [
1684
			'code' => 'cantdelete',
1685
			'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
1686
		],
1687
		'notanarticle' => [
1688
			'code' => 'missingtitle',
1689
			'info' => "The page you requested doesn't exist"
1690
		],
1691
		'selfmove' => [ 'code' => 'selfmove', 'info' => "Can't move a page to itself"
1692
		],
1693
		'immobile_namespace' => [
1694
			'code' => 'immobilenamespace',
1695
			'info' => 'You tried to move pages from or to a namespace that is protected from moving'
1696
		],
1697
		'articleexists' => [
1698
			'code' => 'articleexists',
1699
			'info' => 'The destination article already exists and is not a redirect to the source article'
1700
		],
1701
		'protectedpage' => [
1702
			'code' => 'protectedpage',
1703
			'info' => "You don't have permission to perform this move"
1704
		],
1705
		'hookaborted' => [
1706
			'code' => 'hookaborted',
1707
			'info' => 'The modification you tried to make was aborted by an extension hook'
1708
		],
1709
		'cantmove-titleprotected' => [
1710
			'code' => 'protectedtitle',
1711
			'info' => 'The destination article has been protected from creation'
1712
		],
1713
		'imagenocrossnamespace' => [
1714
			'code' => 'nonfilenamespace',
1715
			'info' => "Can't move a file to a non-file namespace"
1716
		],
1717
		'imagetypemismatch' => [
1718
			'code' => 'filetypemismatch',
1719
			'info' => "The new file extension doesn't match its type"
1720
		],
1721
		// 'badarticleerror' => shouldn't happen
1722
		// 'badtitletext' => shouldn't happen
1723
		'ip_range_invalid' => [ 'code' => 'invalidrange', 'info' => 'Invalid IP range' ],
1724
		'range_block_disabled' => [
1725
			'code' => 'rangedisabled',
1726
			'info' => 'Blocking IP ranges has been disabled'
1727
		],
1728
		'nosuchusershort' => [
1729
			'code' => 'nosuchuser',
1730
			'info' => "The user you specified doesn't exist"
1731
		],
1732
		'badipaddress' => [ 'code' => 'invalidip', 'info' => 'Invalid IP address specified' ],
1733
		'ipb_expiry_invalid' => [ 'code' => 'invalidexpiry', 'info' => 'Invalid expiry time' ],
1734
		'ipb_already_blocked' => [
1735
			'code' => 'alreadyblocked',
1736
			'info' => 'The user you tried to block was already blocked'
1737
		],
1738
		'ipb_blocked_as_range' => [
1739
			'code' => 'blockedasrange',
1740
			'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole."
1741
		],
1742
		'ipb_cant_unblock' => [
1743
			'code' => 'cantunblock',
1744
			'info' => 'The block you specified was not found. It may have been unblocked already'
1745
		],
1746
		'mailnologin' => [
1747
			'code' => 'cantsend',
1748
			'info' => 'You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email'
1749
		],
1750
		'ipbblocked' => [
1751
			'code' => 'ipbblocked',
1752
			'info' => 'You cannot block or unblock users while you are yourself blocked'
1753
		],
1754
		'ipbnounblockself' => [
1755
			'code' => 'ipbnounblockself',
1756
			'info' => 'You are not allowed to unblock yourself'
1757
		],
1758
		'usermaildisabled' => [
1759
			'code' => 'usermaildisabled',
1760
			'info' => 'User email has been disabled'
1761
		],
1762
		'blockedemailuser' => [
1763
			'code' => 'blockedfrommail',
1764
			'info' => 'You have been blocked from sending email'
1765
		],
1766
		'notarget' => [
1767
			'code' => 'notarget',
1768
			'info' => 'You have not specified a valid target for this action'
1769
		],
1770
		'noemail' => [
1771
			'code' => 'noemail',
1772
			'info' => 'The user has not specified a valid email address, or has chosen not to receive email from other users'
1773
		],
1774
		'rcpatroldisabled' => [
1775
			'code' => 'patroldisabled',
1776
			'info' => 'Patrolling is disabled on this wiki'
1777
		],
1778
		'markedaspatrollederror-noautopatrol' => [
1779
			'code' => 'noautopatrol',
1780
			'info' => "You don't have permission to patrol your own changes"
1781
		],
1782
		'delete-toobig' => [
1783
			'code' => 'bigdelete',
1784
			'info' => "You can't delete this page because it has more than \$1 revisions"
1785
		],
1786
		'movenotallowedfile' => [
1787
			'code' => 'cantmovefile',
1788
			'info' => "You don't have permission to move files"
1789
		],
1790
		'userrights-no-interwiki' => [
1791
			'code' => 'nointerwikiuserrights',
1792
			'info' => "You don't have permission to change user rights on other wikis"
1793
		],
1794
		'userrights-nodatabase' => [
1795
			'code' => 'nosuchdatabase',
1796
			'info' => "Database \"\$1\" does not exist or is not local"
1797
		],
1798
		'nouserspecified' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1799
		'noname' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1800
		'summaryrequired' => [ 'code' => 'summaryrequired', 'info' => 'Summary required' ],
1801
		'import-rootpage-invalid' => [
1802
			'code' => 'import-rootpage-invalid',
1803
			'info' => 'Root page is an invalid title'
1804
		],
1805
		'import-rootpage-nosubpage' => [
1806
			'code' => 'import-rootpage-nosubpage',
1807
			'info' => 'Namespace "$1" of the root page does not allow subpages'
1808
		],
1809
1810
		// API-specific messages
1811
		'readrequired' => [
1812
			'code' => 'readapidenied',
1813
			'info' => 'You need read permission to use this module'
1814
		],
1815
		'writedisabled' => [
1816
			'code' => 'noapiwrite',
1817
			'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file"
1818
		],
1819
		'writerequired' => [
1820
			'code' => 'writeapidenied',
1821
			'info' => "You're not allowed to edit this wiki through the API"
1822
		],
1823
		'missingparam' => [ 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ],
1824
		'invalidtitle' => [ 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ],
1825
		'nosuchpageid' => [ 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ],
1826
		'nosuchrevid' => [ 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ],
1827
		'nosuchuser' => [ 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ],
1828
		'invaliduser' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1829
		'invalidexpiry' => [ 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ],
1830
		'pastexpiry' => [ 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ],
1831
		'create-titleexists' => [
1832
			'code' => 'create-titleexists',
1833
			'info' => "Existing titles can't be protected with 'create'"
1834
		],
1835
		'missingtitle-createonly' => [
1836
			'code' => 'missingtitle-createonly',
1837
			'info' => "Missing titles can only be protected with 'create'"
1838
		],
1839
		'cantblock' => [ 'code' => 'cantblock',
1840
			'info' => "You don't have permission to block users"
1841
		],
1842
		'canthide' => [
1843
			'code' => 'canthide',
1844
			'info' => "You don't have permission to hide user names from the block log"
1845
		],
1846
		'cantblock-email' => [
1847
			'code' => 'cantblock-email',
1848
			'info' => "You don't have permission to block users from sending email through the wiki"
1849
		],
1850
		'unblock-notarget' => [
1851
			'code' => 'notarget',
1852
			'info' => 'Either the id or the user parameter must be set'
1853
		],
1854
		'unblock-idanduser' => [
1855
			'code' => 'idanduser',
1856
			'info' => "The id and user parameters can't be used together"
1857
		],
1858
		'cantunblock' => [
1859
			'code' => 'permissiondenied',
1860
			'info' => "You don't have permission to unblock users"
1861
		],
1862
		'cannotundelete' => [
1863
			'code' => 'cantundelete',
1864
			'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
1865
		],
1866
		'permdenied-undelete' => [
1867
			'code' => 'permissiondenied',
1868
			'info' => "You don't have permission to restore deleted revisions"
1869
		],
1870
		'createonly-exists' => [
1871
			'code' => 'articleexists',
1872
			'info' => 'The article you tried to create has been created already'
1873
		],
1874
		'nocreate-missing' => [
1875
			'code' => 'missingtitle',
1876
			'info' => "The article you tried to edit doesn't exist"
1877
		],
1878
		'cantchangecontentmodel' => [
1879
			'code' => 'cantchangecontentmodel',
1880
			'info' => "You don't have permission to change the content model of a page"
1881
		],
1882
		'nosuchrcid' => [
1883
			'code' => 'nosuchrcid',
1884
			'info' => "There is no change with rcid \"\$1\""
1885
		],
1886
		'nosuchlogid' => [
1887
			'code' => 'nosuchlogid',
1888
			'info' => "There is no log entry with ID \"\$1\""
1889
		],
1890
		'protect-invalidaction' => [
1891
			'code' => 'protect-invalidaction',
1892
			'info' => "Invalid protection type \"\$1\""
1893
		],
1894
		'protect-invalidlevel' => [
1895
			'code' => 'protect-invalidlevel',
1896
			'info' => "Invalid protection level \"\$1\""
1897
		],
1898
		'toofewexpiries' => [
1899
			'code' => 'toofewexpiries',
1900
			'info' => "\$1 expiry timestamps were provided where \$2 were needed"
1901
		],
1902
		'cantimport' => [
1903
			'code' => 'cantimport',
1904
			'info' => "You don't have permission to import pages"
1905
		],
1906
		'cantimport-upload' => [
1907
			'code' => 'cantimport-upload',
1908
			'info' => "You don't have permission to import uploaded pages"
1909
		],
1910
		'importnofile' => [ 'code' => 'nofile', 'info' => "You didn't upload a file" ],
1911
		'importuploaderrorsize' => [
1912
			'code' => 'filetoobig',
1913
			'info' => 'The file you uploaded is bigger than the maximum upload size'
1914
		],
1915
		'importuploaderrorpartial' => [
1916
			'code' => 'partialupload',
1917
			'info' => 'The file was only partially uploaded'
1918
		],
1919
		'importuploaderrortemp' => [
1920
			'code' => 'notempdir',
1921
			'info' => 'The temporary upload directory is missing'
1922
		],
1923
		'importcantopen' => [
1924
			'code' => 'cantopenfile',
1925
			'info' => "Couldn't open the uploaded file"
1926
		],
1927
		'import-noarticle' => [
1928
			'code' => 'badinterwiki',
1929
			'info' => 'Invalid interwiki title specified'
1930
		],
1931
		'importbadinterwiki' => [
1932
			'code' => 'badinterwiki',
1933
			'info' => 'Invalid interwiki title specified'
1934
		],
1935
		'import-unknownerror' => [
1936
			'code' => 'import-unknownerror',
1937
			'info' => "Unknown error on import: \"\$1\""
1938
		],
1939
		'cantoverwrite-sharedfile' => [
1940
			'code' => 'cantoverwrite-sharedfile',
1941
			'info' => 'The target file exists on a shared repository and you do not have permission to override it'
1942
		],
1943
		'sharedfile-exists' => [
1944
			'code' => 'fileexists-sharedrepo-perm',
1945
			'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
1946
		],
1947
		'mustbeposted' => [
1948
			'code' => 'mustbeposted',
1949
			'info' => "The \$1 module requires a POST request"
1950
		],
1951
		'show' => [
1952
			'code' => 'show',
1953
			'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
1954
		],
1955
		'specialpage-cantexecute' => [
1956
			'code' => 'specialpage-cantexecute',
1957
			'info' => "You don't have permission to view the results of this special page"
1958
		],
1959
		'invalidoldimage' => [
1960
			'code' => 'invalidoldimage',
1961
			'info' => 'The oldimage parameter has invalid format'
1962
		],
1963
		'nodeleteablefile' => [
1964
			'code' => 'nodeleteablefile',
1965
			'info' => 'No such old version of the file'
1966
		],
1967
		'fileexists-forbidden' => [
1968
			'code' => 'fileexists-forbidden',
1969
			'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
1970
		],
1971
		'fileexists-shared-forbidden' => [
1972
			'code' => 'fileexists-shared-forbidden',
1973
			'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
1974
		],
1975
		'filerevert-badversion' => [
1976
			'code' => 'filerevert-badversion',
1977
			'info' => 'There is no previous local version of this file with the provided timestamp.'
1978
		],
1979
1980
		// ApiEditPage messages
1981
		'noimageredirect-anon' => [
1982
			'code' => 'noimageredirect-anon',
1983
			'info' => "Anonymous users can't create image redirects"
1984
		],
1985
		'noimageredirect-logged' => [
1986
			'code' => 'noimageredirect',
1987
			'info' => "You don't have permission to create image redirects"
1988
		],
1989
		'spamdetected' => [
1990
			'code' => 'spamdetected',
1991
			'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
1992
		],
1993
		'contenttoobig' => [
1994
			'code' => 'contenttoobig',
1995
			'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
1996
		],
1997
		'noedit-anon' => [ 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ],
1998
		'noedit' => [ 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ],
1999
		'wasdeleted' => [
2000
			'code' => 'pagedeleted',
2001
			'info' => 'The page has been deleted since you fetched its timestamp'
2002
		],
2003
		'blankpage' => [
2004
			'code' => 'emptypage',
2005
			'info' => 'Creating new, empty pages is not allowed'
2006
		],
2007
		'editconflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
2008
		'hashcheckfailed' => [ 'code' => 'badmd5', 'info' => 'The supplied MD5 hash was incorrect' ],
2009
		'missingtext' => [
2010
			'code' => 'notext',
2011
			'info' => 'One of the text, appendtext, prependtext and undo parameters must be set'
2012
		],
2013
		'emptynewsection' => [
2014
			'code' => 'emptynewsection',
2015
			'info' => 'Creating empty new sections is not possible.'
2016
		],
2017
		'revwrongpage' => [
2018
			'code' => 'revwrongpage',
2019
			'info' => "r\$1 is not a revision of \"\$2\""
2020
		],
2021
		'undo-failure' => [
2022
			'code' => 'undofailure',
2023
			'info' => 'Undo failed due to conflicting intermediate edits'
2024
		],
2025
		'content-not-allowed-here' => [
2026
			'code' => 'contentnotallowedhere',
2027
			'info' => 'Content model "$1" is not allowed at title "$2"'
2028
		],
2029
2030
		// Messages from WikiPage::doEit(]
2031
		'edit-hook-aborted' => [
2032
			'code' => 'edit-hook-aborted',
2033
			'info' => 'Your edit was aborted by an ArticleSave hook'
2034
		],
2035
		'edit-gone-missing' => [
2036
			'code' => 'edit-gone-missing',
2037
			'info' => "The page you tried to edit doesn't seem to exist anymore"
2038
		],
2039
		'edit-conflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
2040
		'edit-already-exists' => [
2041
			'code' => 'edit-already-exists',
2042
			'info' => 'It seems the page you tried to create already exist'
2043
		],
2044
2045
		// uploadMsgs
2046
		'invalid-file-key' => [ 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ],
2047
		'nouploadmodule' => [ 'code' => 'nouploadmodule', 'info' => 'No upload module set' ],
2048
		'uploaddisabled' => [
2049
			'code' => 'uploaddisabled',
2050
			'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
2051
		],
2052
		'copyuploaddisabled' => [
2053
			'code' => 'copyuploaddisabled',
2054
			'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
2055
		],
2056
		'copyuploadbaddomain' => [
2057
			'code' => 'copyuploadbaddomain',
2058
			'info' => 'Uploads by URL are not allowed from this domain.'
2059
		],
2060
		'copyuploadbadurl' => [
2061
			'code' => 'copyuploadbadurl',
2062
			'info' => 'Upload not allowed from this URL.'
2063
		],
2064
2065
		'filename-tooshort' => [
2066
			'code' => 'filename-tooshort',
2067
			'info' => 'The filename is too short'
2068
		],
2069
		'filename-toolong' => [ 'code' => 'filename-toolong', 'info' => 'The filename is too long' ],
2070
		'illegal-filename' => [
2071
			'code' => 'illegal-filename',
2072
			'info' => 'The filename is not allowed'
2073
		],
2074
		'filetype-missing' => [
2075
			'code' => 'filetype-missing',
2076
			'info' => 'The file is missing an extension'
2077
		],
2078
2079
		'mustbeloggedin' => [ 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' ]
2080
	];
2081
	// @codingStandardsIgnoreEnd
2082
2083
	/**
2084
	 * Helper function for readonly errors
2085
	 *
2086
	 * @throws UsageException always
2087
	 */
2088
	public function dieReadOnly() {
2089
		$parsed = $this->parseMsg( [ 'readonlytext' ] );
2090
		$this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0,
2091
			[ 'readonlyreason' => wfReadOnlyReason() ] );
2092
	}
2093
2094
	/**
2095
	 * Output the error message related to a certain array
2096
	 * @param array|string $error Element of a getUserPermissionsErrors()-style array
2097
	 * @throws UsageException always
2098
	 */
2099
	public function dieUsageMsg( $error ) {
2100
		# most of the time we send a 1 element, so we might as well send it as
2101
		# a string and make this an array here.
2102
		if ( is_string( $error ) ) {
2103
			$error = [ $error ];
2104
		}
2105
		$parsed = $this->parseMsg( $error );
2106
		$extraData = isset( $parsed['data'] ) ? $parsed['data'] : null;
2107
		$this->dieUsage( $parsed['info'], $parsed['code'], 0, $extraData );
2108
	}
2109
2110
	/**
2111
	 * Will only set a warning instead of failing if the global $wgDebugAPI
2112
	 * is set to true. Otherwise behaves exactly as dieUsageMsg().
2113
	 * @param array|string $error Element of a getUserPermissionsErrors()-style array
2114
	 * @throws UsageException
2115
	 * @since 1.21
2116
	 */
2117
	public function dieUsageMsgOrDebug( $error ) {
2118
		if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2119
			$this->dieUsageMsg( $error );
2120
		}
2121
2122
		if ( is_string( $error ) ) {
2123
			$error = [ $error ];
2124
		}
2125
		$parsed = $this->parseMsg( $error );
2126
		$this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
2127
	}
2128
2129
	/**
2130
	 * Die with the $prefix.'badcontinue' error. This call is common enough to
2131
	 * make it into the base method.
2132
	 * @param bool $condition Will only die if this value is true
2133
	 * @throws UsageException
2134
	 * @since 1.21
2135
	 */
2136
	protected function dieContinueUsageIf( $condition ) {
2137
		if ( $condition ) {
2138
			$this->dieUsage(
2139
				'Invalid continue param. You should pass the original value returned by the previous query',
2140
				'badcontinue' );
2141
		}
2142
	}
2143
2144
	/**
2145
	 * Return the error message related to a certain array
2146
	 * @param array $error Element of a getUserPermissionsErrors()-style array
2147
	 * @return array('code' => code, 'info' => info)
0 ignored issues
show
Documentation introduced by
The doc-type array('code' could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2148
	 */
2149
	public function parseMsg( $error ) {
2150
		$error = (array)$error; // It seems strings sometimes make their way in here
2151
		$key = array_shift( $error );
2152
2153
		// Check whether the error array was nested
2154
		// array( array( <code>, <params> ), array( <another_code>, <params> ) )
2155
		if ( is_array( $key ) ) {
2156
			$error = $key;
2157
			$key = array_shift( $error );
2158
		}
2159
2160
		if ( $key instanceof IApiMessage ) {
2161
			return [
2162
				'code' => $key->getApiCode(),
2163
				'info' => $key->inLanguage( 'en' )->useDatabase( false )->text(),
2164
				'data' => $key->getApiData()
2165
			];
2166
		}
2167
2168
		if ( isset( self::$messageMap[$key] ) ) {
2169
			return [
2170
				'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
2171
				'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
2172
			];
2173
		}
2174
2175
		// If the key isn't present, throw an "unknown error"
2176
		return $this->parseMsg( [ 'unknownerror', $key ] );
2177
	}
2178
2179
	/**
2180
	 * Internal code errors should be reported with this method
2181
	 * @param string $method Method or function name
2182
	 * @param string $message Error message
2183
	 * @throws MWException always
2184
	 */
2185
	protected static function dieDebug( $method, $message ) {
2186
		throw new MWException( "Internal error in $method: $message" );
2187
	}
2188
2189
	/**
2190
	 * Write logging information for API features to a debug log, for usage
2191
	 * analysis.
2192
	 * @param string $feature Feature being used.
2193
	 */
2194
	protected function logFeatureUsage( $feature ) {
2195
		$request = $this->getRequest();
2196
		$s = '"' . addslashes( $feature ) . '"' .
2197
			' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
2198
			' "' . $request->getIP() . '"' .
2199
			' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
2200
			' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
2201
		wfDebugLog( 'api-feature-usage', $s, 'private' );
2202
	}
2203
2204
	/**@}*/
2205
2206
	/************************************************************************//**
2207
	 * @name   Help message generation
2208
	 * @{
2209
	 */
2210
2211
	/**
2212
	 * Return the description message.
2213
	 *
2214
	 * @return string|array|Message
2215
	 */
2216
	protected function getDescriptionMessage() {
2217
		return "apihelp-{$this->getModulePath()}-description";
2218
	}
2219
2220
	/**
2221
	 * Get final module description, after hooks have had a chance to tweak it as
2222
	 * needed.
2223
	 *
2224
	 * @since 1.25, returns Message[] rather than string[]
2225
	 * @return Message[]
2226
	 */
2227
	public function getFinalDescription() {
2228
		$desc = $this->getDescription();
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::getDescription() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2229
		Hooks::run( 'APIGetDescription', [ &$this, &$desc ] );
2230
		$desc = self::escapeWikiText( $desc );
2231
		if ( is_array( $desc ) ) {
2232
			$desc = implode( "\n", $desc );
2233
		} else {
2234
			$desc = (string)$desc;
2235
		}
2236
2237
		$msg = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
2238
			$this->getModulePrefix(),
2239
			$this->getModuleName(),
2240
			$this->getModulePath(),
2241
		] );
2242
		if ( !$msg->exists() ) {
2243
			$msg = $this->msg( 'api-help-fallback-description', $desc );
2244
		}
2245
		$msgs = [ $msg ];
2246
2247
		Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2248
2249
		return $msgs;
2250
	}
2251
2252
	/**
2253
	 * Get final list of parameters, after hooks have had a chance to
2254
	 * tweak it as needed.
2255
	 *
2256
	 * @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
2257
	 * @return array|bool False on no parameters
2258
	 * @since 1.21 $flags param added
2259
	 */
2260
	public function getFinalParams( $flags = 0 ) {
2261
		$params = $this->getAllowedParams( $flags );
2262
		if ( !$params ) {
2263
			$params = [];
2264
		}
2265
2266
		if ( $this->needsToken() ) {
2267
			$params['token'] = [
2268
				ApiBase::PARAM_TYPE => 'string',
2269
				ApiBase::PARAM_REQUIRED => true,
2270
				ApiBase::PARAM_HELP_MSG => [
2271
					'api-help-param-token',
2272
					$this->needsToken(),
2273
				],
2274
			] + ( isset( $params['token'] ) ? $params['token'] : [] );
2275
		}
2276
2277
		Hooks::run( 'APIGetAllowedParams', [ &$this, &$params, $flags ] );
2278
2279
		return $params;
2280
	}
2281
2282
	/**
2283
	 * Get final parameter descriptions, after hooks have had a chance to tweak it as
2284
	 * needed.
2285
	 *
2286
	 * @since 1.25, returns array of Message[] rather than array of string[]
2287
	 * @return array Keys are parameter names, values are arrays of Message objects
2288
	 */
2289
	public function getFinalParamDescription() {
2290
		$prefix = $this->getModulePrefix();
2291
		$name = $this->getModuleName();
2292
		$path = $this->getModulePath();
2293
2294
		$desc = $this->getParamDescription();
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::getParamDescription() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2295
		Hooks::run( 'APIGetParamDescription', [ &$this, &$desc ] );
2296
2297
		if ( !$desc ) {
2298
			$desc = [];
2299
		}
2300
		$desc = self::escapeWikiText( $desc );
2301
2302
		$params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
2303
		$msgs = [];
2304
		foreach ( $params as $param => $settings ) {
2305
			if ( !is_array( $settings ) ) {
2306
				$settings = [];
2307
			}
2308
2309
			$d = isset( $desc[$param] ) ? $desc[$param] : '';
2310
			if ( is_array( $d ) ) {
2311
				// Special handling for prop parameters
2312
				$d = array_map( function ( $line ) {
2313
					if ( preg_match( '/^\s+(\S+)\s+-\s+(.+)$/', $line, $m ) ) {
2314
						$line = "\n;{$m[1]}:{$m[2]}";
2315
					}
2316
					return $line;
2317
				}, $d );
2318
				$d = implode( ' ', $d );
2319
			}
2320
2321
			if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
2322
				$msg = $settings[ApiBase::PARAM_HELP_MSG];
2323
			} else {
2324
				$msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2325
				if ( !$msg->exists() ) {
2326
					$msg = $this->msg( 'api-help-fallback-parameter', $d );
2327
				}
2328
			}
2329
			$msg = ApiBase::makeMessage( $msg, $this->getContext(),
2330
				[ $prefix, $param, $name, $path ] );
2331
			if ( !$msg ) {
2332
				self::dieDebug( __METHOD__,
2333
					'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2334
			}
2335
			$msgs[$param] = [ $msg ];
2336
2337
			if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
2338
				if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
2339
					self::dieDebug( __METHOD__,
2340
						'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2341
				}
2342
				if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) {
2343
					self::dieDebug( __METHOD__,
2344
						'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2345
						'ApiBase::PARAM_TYPE is an array' );
2346
				}
2347
2348
				$valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE];
2349
				foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) {
2350
					if ( isset( $valueMsgs[$value] ) ) {
2351
						$msg = $valueMsgs[$value];
2352
					} else {
2353
						$msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2354
					}
2355
					$m = ApiBase::makeMessage( $msg, $this->getContext(),
2356
						[ $prefix, $param, $name, $path, $value ] );
2357
					if ( $m ) {
2358
						$m = new ApiHelpParamValueMessage(
2359
							$value,
2360
							[ $m->getKey(), 'api-help-param-no-description' ],
2361
							$m->getParams()
2362
						);
2363
						$msgs[$param][] = $m->setContext( $this->getContext() );
2364
					} else {
2365
						self::dieDebug( __METHOD__,
2366
							"Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2367
					}
2368
				}
2369
			}
2370
2371
			if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
2372
				if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
2373
					self::dieDebug( __METHOD__,
2374
						'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2375
				}
2376
				foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
2377
					$m = ApiBase::makeMessage( $m, $this->getContext(),
2378
						[ $prefix, $param, $name, $path ] );
2379
					if ( $m ) {
2380
						$msgs[$param][] = $m;
2381
					} else {
2382
						self::dieDebug( __METHOD__,
2383
							'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2384
					}
2385
				}
2386
			}
2387
		}
2388
2389
		Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2390
2391
		return $msgs;
2392
	}
2393
2394
	/**
2395
	 * Generates the list of flags for the help screen and for action=paraminfo
2396
	 *
2397
	 * Corresponding messages: api-help-flag-deprecated,
2398
	 * api-help-flag-internal, api-help-flag-readrights,
2399
	 * api-help-flag-writerights, api-help-flag-mustbeposted
2400
	 *
2401
	 * @return string[]
2402
	 */
2403
	protected function getHelpFlags() {
2404
		$flags = [];
2405
2406
		if ( $this->isDeprecated() ) {
2407
			$flags[] = 'deprecated';
2408
		}
2409
		if ( $this->isInternal() ) {
2410
			$flags[] = 'internal';
2411
		}
2412
		if ( $this->isReadMode() ) {
2413
			$flags[] = 'readrights';
2414
		}
2415
		if ( $this->isWriteMode() ) {
2416
			$flags[] = 'writerights';
2417
		}
2418
		if ( $this->mustBePosted() ) {
2419
			$flags[] = 'mustbeposted';
2420
		}
2421
2422
		return $flags;
2423
	}
2424
2425
	/**
2426
	 * Returns information about the source of this module, if known
2427
	 *
2428
	 * Returned array is an array with the following keys:
2429
	 * - path: Install path
2430
	 * - name: Extension name, or "MediaWiki" for core
2431
	 * - namemsg: (optional) i18n message key for a display name
2432
	 * - license-name: (optional) Name of license
2433
	 *
2434
	 * @return array|null
2435
	 */
2436
	protected function getModuleSourceInfo() {
2437
		global $IP;
2438
2439
		if ( $this->mModuleSource !== false ) {
2440
			return $this->mModuleSource;
2441
		}
2442
2443
		// First, try to find where the module comes from...
2444
		$rClass = new ReflectionClass( $this );
2445
		$path = $rClass->getFileName();
2446
		if ( !$path ) {
2447
			// No path known?
2448
			$this->mModuleSource = null;
2449
			return null;
2450
		}
2451
		$path = realpath( $path ) ?: $path;
2452
2453
		// Build map of extension directories to extension info
2454
		if ( self::$extensionInfo === null ) {
2455
			self::$extensionInfo = [
2456
				realpath( __DIR__ ) ?: __DIR__ => [
2457
					'path' => $IP,
2458
					'name' => 'MediaWiki',
2459
					'license-name' => 'GPL-2.0+',
2460
				],
2461
				realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2462
			];
2463
			$keep = [
2464
				'path' => null,
2465
				'name' => null,
2466
				'namemsg' => null,
2467
				'license-name' => null,
2468
			];
2469
			foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2470
				foreach ( $group as $ext ) {
2471
					if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2472
						// This shouldn't happen, but does anyway.
2473
						continue;
2474
					}
2475
2476
					$extpath = $ext['path'];
2477
					if ( !is_dir( $extpath ) ) {
2478
						$extpath = dirname( $extpath );
2479
					}
2480
					self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2481
						array_intersect_key( $ext, $keep );
2482
				}
2483
			}
2484
			foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2485
				$extpath = $ext['path'];
2486
				if ( !is_dir( $extpath ) ) {
2487
					$extpath = dirname( $extpath );
2488
				}
2489
				self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2490
					array_intersect_key( $ext, $keep );
2491
			}
2492
		}
2493
2494
		// Now traverse parent directories until we find a match or run out of
2495
		// parents.
2496
		do {
2497
			if ( array_key_exists( $path, self::$extensionInfo ) ) {
2498
				// Found it!
2499
				$this->mModuleSource = self::$extensionInfo[$path];
2500
				return $this->mModuleSource;
2501
			}
2502
2503
			$oldpath = $path;
2504
			$path = dirname( $path );
2505
		} while ( $path !== $oldpath );
2506
2507
		// No idea what extension this might be.
2508
		$this->mModuleSource = null;
2509
		return null;
2510
	}
2511
2512
	/**
2513
	 * Called from ApiHelp before the pieces are joined together and returned.
2514
	 *
2515
	 * This exists mainly for ApiMain to add the Permissions and Credits
2516
	 * sections. Other modules probably don't need it.
2517
	 *
2518
	 * @param string[] &$help Array of help data
2519
	 * @param array $options Options passed to ApiHelp::getHelp
2520
	 * @param array &$tocData If a TOC is being generated, this array has keys
2521
	 *   as anchors in the page and values as for Linker::generateTOC().
2522
	 */
2523
	public function modifyHelp( array &$help, array $options, array &$tocData ) {
2524
	}
2525
2526
	/**@}*/
2527
2528
	/************************************************************************//**
2529
	 * @name   Deprecated
2530
	 * @{
2531
	 */
2532
2533
	/**
2534
	 * Returns the description string for this module
2535
	 *
2536
	 * Ignored if an i18n message exists for
2537
	 * "apihelp-{$this->getModulePath()}-description".
2538
	 *
2539
	 * @deprecated since 1.25
2540
	 * @return Message|string|array
2541
	 */
2542
	protected function getDescription() {
2543
		return false;
2544
	}
2545
2546
	/**
2547
	 * Returns an array of parameter descriptions.
2548
	 *
2549
	 * For each parameter, ignored if an i18n message exists for the parameter.
2550
	 * By default that message is
2551
	 * "apihelp-{$this->getModulePath()}-param-{$param}", but it may be
2552
	 * overridden using ApiBase::PARAM_HELP_MSG in the data returned by
2553
	 * self::getFinalParams().
2554
	 *
2555
	 * @deprecated since 1.25
2556
	 * @return array|bool False on no parameter descriptions
2557
	 */
2558
	protected function getParamDescription() {
2559
		return [];
2560
	}
2561
2562
	/**
2563
	 * Returns usage examples for this module.
2564
	 *
2565
	 * Return value as an array is either:
2566
	 *  - numeric keys with partial URLs ("api.php?" plus a query string) as
2567
	 *    values
2568
	 *  - sequential numeric keys with even-numbered keys being display-text
2569
	 *    and odd-numbered keys being partial urls
2570
	 *  - partial URLs as keys with display-text (string or array-to-be-joined)
2571
	 *    as values
2572
	 * Return value as a string is the same as an array with a numeric key and
2573
	 * that value, and boolean false means "no examples".
2574
	 *
2575
	 * @deprecated since 1.25, use getExamplesMessages() instead
2576
	 * @return bool|string|array
2577
	 */
2578
	protected function getExamples() {
2579
		return false;
2580
	}
2581
2582
	/**
2583
	 * Generates help message for this module, or false if there is no description
2584
	 * @deprecated since 1.25
2585
	 * @return string|bool
2586
	 */
2587
	public function makeHelpMsg() {
2588
		wfDeprecated( __METHOD__, '1.25' );
2589
		static $lnPrfx = "\n  ";
2590
2591
		$msg = $this->getFinalDescription();
2592
2593
		if ( $msg !== false ) {
2594
2595
			if ( !is_array( $msg ) ) {
2596
				$msg = [
2597
					$msg
2598
				];
2599
			}
2600
			$msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
2601
2602
			$msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::makeHelpArrayToString() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2603
2604
			if ( $this->isReadMode() ) {
2605
				$msg .= "\nThis module requires read rights";
2606
			}
2607
			if ( $this->isWriteMode() ) {
2608
				$msg .= "\nThis module requires write rights";
2609
			}
2610
			if ( $this->mustBePosted() ) {
2611
				$msg .= "\nThis module only accepts POST requests";
2612
			}
2613
			if ( $this->isReadMode() || $this->isWriteMode() ||
2614
				$this->mustBePosted()
2615
			) {
2616
				$msg .= "\n";
2617
			}
2618
2619
			// Parameters
2620
			$paramsMsg = $this->makeHelpMsgParameters();
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::makeHelpMsgParameters() has been deprecated with message: since 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2621
			if ( $paramsMsg !== false ) {
2622
				$msg .= "Parameters:\n$paramsMsg";
2623
			}
2624
2625
			$examples = $this->getExamples();
0 ignored issues
show
Deprecated Code introduced by
The method ApiBase::getExamples() has been deprecated with message: since 1.25, use getExamplesMessages() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2626
			if ( $examples ) {
2627
				if ( !is_array( $examples ) ) {
2628
					$examples = [
2629
						$examples
2630
					];
2631
				}
2632
				$msg .= 'Example' . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
2633
				foreach ( $examples as $k => $v ) {
2634
					if ( is_numeric( $k ) ) {
2635
						$msg .= "  $v\n";
2636
					} else {
2637 View Code Duplication
						if ( is_array( $v ) ) {
2638
							$msgExample = implode( "\n", array_map( [ $this, 'indentExampleText' ], $v ) );
2639
						} else {
2640
							$msgExample = "  $v";
2641
						}
2642
						$msgExample .= ':';
2643
						$msg .= wordwrap( $msgExample, 100, "\n" ) . "\n    $k\n";
2644
					}
2645
				}
2646
			}
2647
		}
2648
2649
		return $msg;
2650
	}
2651
2652
	/**
2653
	 * @deprecated since 1.25
2654
	 * @param string $item
2655
	 * @return string
2656
	 */
2657
	private function indentExampleText( $item ) {
2658
		return '  ' . $item;
2659
	}
2660
2661
	/**
2662
	 * @deprecated since 1.25
2663
	 * @param string $prefix Text to split output items
2664
	 * @param string $title What is being output
2665
	 * @param string|array $input
2666
	 * @return string
2667
	 */
2668
	protected function makeHelpArrayToString( $prefix, $title, $input ) {
2669
		wfDeprecated( __METHOD__, '1.25' );
2670
		if ( $input === false ) {
2671
			return '';
2672
		}
2673
		if ( !is_array( $input ) ) {
2674
			$input = [ $input ];
2675
		}
2676
2677
		if ( count( $input ) > 0 ) {
2678
			if ( $title ) {
2679
				$msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n  ";
2680
			} else {
2681
				$msg = '  ';
2682
			}
2683
			$msg .= implode( $prefix, $input ) . "\n";
2684
2685
			return $msg;
2686
		}
2687
2688
		return '';
2689
	}
2690
2691
	/**
2692
	 * Generates the parameter descriptions for this module, to be displayed in the
2693
	 * module's help.
2694
	 * @deprecated since 1.25
2695
	 * @return string|bool
2696
	 */
2697
	public function makeHelpMsgParameters() {
2698
		wfDeprecated( __METHOD__, '1.25' );
2699
		$params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
2700
		if ( $params ) {
2701
			$paramsDescription = $this->getFinalParamDescription();
2702
			$msg = '';
2703
			$paramPrefix = "\n" . str_repeat( ' ', 24 );
2704
			$descWordwrap = "\n" . str_repeat( ' ', 28 );
2705
			foreach ( $params as $paramName => $paramSettings ) {
2706
				$desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
2707
				if ( is_array( $desc ) ) {
2708
					$desc = implode( $paramPrefix, $desc );
2709
				}
2710
2711
				// handle shorthand
2712
				if ( !is_array( $paramSettings ) ) {
2713
					$paramSettings = [
2714
						self::PARAM_DFLT => $paramSettings,
2715
					];
2716
				}
2717
2718
				// handle missing type
2719 View Code Duplication
				if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
2720
					$dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
2721
						? $paramSettings[ApiBase::PARAM_DFLT]
2722
						: null;
2723
					if ( is_bool( $dflt ) ) {
2724
						$paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
2725
					} elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
2726
						$paramSettings[ApiBase::PARAM_TYPE] = 'string';
2727
					} elseif ( is_int( $dflt ) ) {
2728
						$paramSettings[ApiBase::PARAM_TYPE] = 'integer';
2729
					}
2730
				}
2731
2732
				if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
2733
					&& $paramSettings[self::PARAM_DEPRECATED]
2734
				) {
2735
					$desc = "DEPRECATED! $desc";
2736
				}
2737
2738
				if ( isset( $paramSettings[self::PARAM_REQUIRED] )
2739
					&& $paramSettings[self::PARAM_REQUIRED]
2740
				) {
2741
					$desc .= $paramPrefix . 'This parameter is required';
2742
				}
2743
2744
				$type = isset( $paramSettings[self::PARAM_TYPE] )
2745
					? $paramSettings[self::PARAM_TYPE]
2746
					: null;
2747
				if ( isset( $type ) ) {
2748
					$hintPipeSeparated = true;
2749
					$multi = isset( $paramSettings[self::PARAM_ISMULTI] )
2750
						? $paramSettings[self::PARAM_ISMULTI]
2751
						: false;
2752
					if ( $multi ) {
2753
						$prompt = 'Values (separate with \'|\'): ';
2754
					} else {
2755
						$prompt = 'One value: ';
2756
					}
2757
2758 View Code Duplication
					if ( $type === 'submodule' ) {
2759
						if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
2760
							$type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
2761
						} else {
2762
							$type = $this->getModuleManager()->getNames( $paramName );
2763
						}
2764
						sort( $type );
2765
					}
2766
					if ( is_array( $type ) ) {
2767
						$choices = [];
2768
						$nothingPrompt = '';
2769
						foreach ( $type as $t ) {
2770
							if ( $t === '' ) {
2771
								$nothingPrompt = 'Can be empty, or ';
2772
							} else {
2773
								$choices[] = $t;
2774
							}
2775
						}
2776
						$desc .= $paramPrefix . $nothingPrompt . $prompt;
2777
						$choicesstring = implode( ', ', $choices );
2778
						$desc .= wordwrap( $choicesstring, 100, $descWordwrap );
2779
						$hintPipeSeparated = false;
2780
					} else {
2781
						switch ( $type ) {
2782
							case 'namespace':
2783
								// Special handling because namespaces are
2784
								// type-limited, yet they are not given
2785
								$desc .= $paramPrefix . $prompt;
2786
								$desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
2787
									100, $descWordwrap );
2788
								$hintPipeSeparated = false;
2789
								break;
2790
							case 'limit':
2791
								$desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
2792
								if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
2793
									$desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
2794
								}
2795
								$desc .= ' allowed';
2796
								break;
2797
							case 'integer':
2798
								$s = $multi ? 's' : '';
2799
								$hasMin = isset( $paramSettings[self::PARAM_MIN] );
2800
								$hasMax = isset( $paramSettings[self::PARAM_MAX] );
2801
								if ( $hasMin || $hasMax ) {
2802
									if ( !$hasMax ) {
2803
										$intRangeStr = "The value$s must be no less than " .
2804
											"{$paramSettings[self::PARAM_MIN]}";
2805
									} elseif ( !$hasMin ) {
2806
										$intRangeStr = "The value$s must be no more than " .
2807
											"{$paramSettings[self::PARAM_MAX]}";
2808
									} else {
2809
										$intRangeStr = "The value$s must be between " .
2810
											"{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
2811
									}
2812
2813
									$desc .= $paramPrefix . $intRangeStr;
2814
								}
2815
								break;
2816
							case 'upload':
2817
								$desc .= $paramPrefix . 'Must be posted as a file upload using multipart/form-data';
2818
								break;
2819
						}
2820
					}
2821
2822
					if ( $multi ) {
2823
						if ( $hintPipeSeparated ) {
2824
							$desc .= $paramPrefix . "Separate values with '|'";
2825
						}
2826
2827
						$isArray = is_array( $type );
2828
						if ( !$isArray
2829
							|| $isArray && count( $type ) > self::LIMIT_SML1
2830
						) {
2831
							$desc .= $paramPrefix . 'Maximum number of values ' .
2832
								self::LIMIT_SML1 . ' (' . self::LIMIT_SML2 . ' for bots)';
2833
						}
2834
					}
2835
				}
2836
2837
				$default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
2838
				if ( !is_null( $default ) && $default !== false ) {
2839
					$desc .= $paramPrefix . "Default: $default";
2840
				}
2841
2842
				$msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
2843
			}
2844
2845
			return $msg;
2846
		}
2847
2848
		return false;
2849
	}
2850
2851
	/**
2852
	 * @deprecated since 1.25, always returns empty string
2853
	 * @param IDatabase|bool $db
2854
	 * @return string
2855
	 */
2856
	public function getModuleProfileName( $db = false ) {
2857
		wfDeprecated( __METHOD__, '1.25' );
2858
		return '';
2859
	}
2860
2861
	/**
2862
	 * @deprecated since 1.25
2863
	 */
2864
	public function profileIn() {
2865
		// No wfDeprecated() yet because extensions call this and might need to
2866
		// keep doing so for BC.
2867
	}
2868
2869
	/**
2870
	 * @deprecated since 1.25
2871
	 */
2872
	public function profileOut() {
2873
		// No wfDeprecated() yet because extensions call this and might need to
2874
		// keep doing so for BC.
2875
	}
2876
2877
	/**
2878
	 * @deprecated since 1.25
2879
	 */
2880
	public function safeProfileOut() {
2881
		wfDeprecated( __METHOD__, '1.25' );
2882
	}
2883
2884
	/**
2885
	 * @deprecated since 1.25, always returns 0
2886
	 * @return float
2887
	 */
2888
	public function getProfileTime() {
2889
		wfDeprecated( __METHOD__, '1.25' );
2890
		return 0;
2891
	}
2892
2893
	/**
2894
	 * @deprecated since 1.25
2895
	 */
2896
	public function profileDBIn() {
2897
		wfDeprecated( __METHOD__, '1.25' );
2898
	}
2899
2900
	/**
2901
	 * @deprecated since 1.25
2902
	 */
2903
	public function profileDBOut() {
2904
		wfDeprecated( __METHOD__, '1.25' );
2905
	}
2906
2907
	/**
2908
	 * @deprecated since 1.25, always returns 0
2909
	 * @return float
2910
	 */
2911
	public function getProfileDBTime() {
2912
		wfDeprecated( __METHOD__, '1.25' );
2913
		return 0;
2914
	}
2915
2916
	/**
2917
	 * Get the result data array (read-only)
2918
	 * @deprecated since 1.25, use $this->getResult() methods instead
2919
	 * @return array
2920
	 */
2921
	public function getResultData() {
2922
		wfDeprecated( __METHOD__, '1.25' );
2923
		return $this->getResult()->getData();
0 ignored issues
show
Deprecated Code introduced by
The method ApiResult::getData() has been deprecated with message: since 1.25, use $this->getResultData() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2924
	}
2925
2926
	/**
2927
	 * Call wfTransactionalTimeLimit() if this request was POSTed
2928
	 * @since 1.26
2929
	 */
2930
	protected function useTransactionalTimeLimit() {
2931
		if ( $this->getRequest()->wasPosted() ) {
2932
			wfTransactionalTimeLimit();
2933
		}
2934
	}
2935
2936
	/**@}*/
2937
}
2938
2939
/**
2940
 * For really cool vim folding this needs to be at the end:
2941
 * vim: foldmarker=@{,@} foldmethod=marker
2942
 */
2943