Completed
Branch master (8ef871)
by
unknown
29:40
created

ApiBase::getResultProperties()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 0
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 &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $examples of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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|boolean|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
		return $this->getMain()->getRequest()->getVal( 'callback' ) !== null;
507
	}
508
509
	/**
510
	 * Get the path to this module
511
	 *
512
	 * @since 1.25
513
	 * @return string
514
	 */
515
	public function getModulePath() {
516
		if ( $this->isMain() ) {
517
			return 'main';
518
		} elseif ( $this->getParent()->isMain() ) {
519
			return $this->getModuleName();
520
		} else {
521
			return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
522
		}
523
	}
524
525
	/**
526
	 * Get a module from its module path
527
	 *
528
	 * @since 1.25
529
	 * @param string $path
530
	 * @return ApiBase|null
531
	 * @throws UsageException
532
	 */
533
	public function getModuleFromPath( $path ) {
534
		$module = $this->getMain();
535
		if ( $path === 'main' ) {
536
			return $module;
537
		}
538
539
		$parts = explode( '+', $path );
540
		if ( count( $parts ) === 1 ) {
541
			// In case the '+' was typed into URL, it resolves as a space
542
			$parts = explode( ' ', $path );
543
		}
544
545
		$count = count( $parts );
546
		for ( $i = 0; $i < $count; $i++ ) {
547
			$parent = $module;
548
			$manager = $parent->getModuleManager();
549
			if ( $manager === null ) {
550
				$errorPath = implode( '+', array_slice( $parts, 0, $i ) );
551
				$this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' );
552
			}
553
			$module = $manager->getModule( $parts[$i] );
554
555
			if ( $module === null ) {
556
				$errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
557
				$this->dieUsage(
558
					"The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"",
559
					'badmodule'
560
				);
561
			}
562
		}
563
564
		return $module;
565
	}
566
567
	/**
568
	 * Get the result object
569
	 * @return ApiResult
570
	 */
571
	public function getResult() {
572
		// Main module has getResult() method overridden
573
		// Safety - avoid infinite loop:
574
		if ( $this->isMain() ) {
575
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
576
		}
577
578
		return $this->getMain()->getResult();
579
	}
580
581
	/**
582
	 * Get the error formatter
583
	 * @return ApiErrorFormatter
584
	 */
585
	public function getErrorFormatter() {
586
		// Main module has getErrorFormatter() method overridden
587
		// Safety - avoid infinite loop:
588
		if ( $this->isMain() ) {
589
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
590
		}
591
592
		return $this->getMain()->getErrorFormatter();
593
	}
594
595
	/**
596
	 * Gets a default slave database connection object
597
	 * @return DatabaseBase
598
	 */
599
	protected function getDB() {
600
		if ( !isset( $this->mSlaveDB ) ) {
601
			$this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
602
		}
603
604
		return $this->mSlaveDB;
605
	}
606
607
	/**
608
	 * Get the continuation manager
609
	 * @return ApiContinuationManager|null
610
	 */
611
	public function getContinuationManager() {
612
		// Main module has getContinuationManager() method overridden
613
		// Safety - avoid infinite loop:
614
		if ( $this->isMain() ) {
615
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
616
		}
617
618
		return $this->getMain()->getContinuationManager();
619
	}
620
621
	/**
622
	 * Set the continuation manager
623
	 * @param ApiContinuationManager|null
624
	 */
625
	public function setContinuationManager( $manager ) {
626
		// Main module has setContinuationManager() method overridden
627
		// Safety - avoid infinite loop:
628
		if ( $this->isMain() ) {
629
			ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
630
		}
631
632
		$this->getMain()->setContinuationManager( $manager );
633
	}
634
635
	/**@}*/
636
637
	/************************************************************************//**
638
	 * @name   Parameter handling
639
	 * @{
640
	 */
641
642
	/**
643
	 * Indicate if the module supports dynamically-determined parameters that
644
	 * cannot be included in self::getAllowedParams().
645
	 * @return string|array|Message|null Return null if the module does not
646
	 *  support additional dynamic parameters, otherwise return a message
647
	 *  describing them.
648
	 */
649
	public function dynamicParameterDocumentation() {
650
		return null;
651
	}
652
653
	/**
654
	 * This method mangles parameter name based on the prefix supplied to the constructor.
655
	 * Override this method to change parameter name during runtime
656
	 * @param string $paramName Parameter name
657
	 * @return string Prefixed parameter name
658
	 */
659
	public function encodeParamName( $paramName ) {
660
		return $this->mModulePrefix . $paramName;
661
	}
662
663
	/**
664
	 * Using getAllowedParams(), this function makes an array of the values
665
	 * provided by the user, with key being the name of the variable, and
666
	 * value - validated value from user or default. limits will not be
667
	 * parsed if $parseLimit is set to false; use this when the max
668
	 * limit is not definitive yet, e.g. when getting revisions.
669
	 * @param bool $parseLimit True by default
670
	 * @return array
671
	 */
672
	public function extractRequestParams( $parseLimit = true ) {
673
		// Cache parameters, for performance and to avoid bug 24564.
674
		if ( !isset( $this->mParamCache[$parseLimit] ) ) {
675
			$params = $this->getFinalParams();
676
			$results = [];
677
678
			if ( $params ) { // getFinalParams() can return false
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
679
				foreach ( $params as $paramName => $paramSettings ) {
680
					$results[$paramName] = $this->getParameterFromSettings(
681
						$paramName, $paramSettings, $parseLimit );
682
				}
683
			}
684
			$this->mParamCache[$parseLimit] = $results;
685
		}
686
687
		return $this->mParamCache[$parseLimit];
688
	}
689
690
	/**
691
	 * Get a value for the given parameter
692
	 * @param string $paramName Parameter name
693
	 * @param bool $parseLimit See extractRequestParams()
694
	 * @return mixed Parameter value
695
	 */
696
	protected function getParameter( $paramName, $parseLimit = true ) {
697
		$paramSettings = $this->getFinalParams()[$paramName];
698
699
		return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
700
	}
701
702
	/**
703
	 * Die if none or more than one of a certain set of parameters is set and not false.
704
	 *
705
	 * @param array $params User provided set of parameters, as from $this->extractRequestParams()
706
	 * @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...
707
	 */
708
	public function requireOnlyOneParameter( $params, $required /*...*/ ) {
709
		$required = func_get_args();
710
		array_shift( $required );
711
		$p = $this->getModulePrefix();
712
713
		$intersection = array_intersect( array_keys( array_filter( $params,
714
			[ $this, 'parameterNotEmpty' ] ) ), $required );
715
716
		if ( count( $intersection ) > 1 ) {
717
			$this->dieUsage(
718
				"The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
719
				'invalidparammix' );
720
		} elseif ( count( $intersection ) == 0 ) {
721
			$this->dieUsage(
722
				"One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
723
				'missingparam'
724
			);
725
		}
726
	}
727
728
	/**
729
	 * Die if more than one of a certain set of parameters is set and not false.
730
	 *
731
	 * @param array $params User provided set of parameters, as from $this->extractRequestParams()
732
	 * @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...
733
	 */
734 View Code Duplication
	public function requireMaxOneParameter( $params, $required /*...*/ ) {
735
		$required = func_get_args();
736
		array_shift( $required );
737
		$p = $this->getModulePrefix();
738
739
		$intersection = array_intersect( array_keys( array_filter( $params,
740
			[ $this, 'parameterNotEmpty' ] ) ), $required );
741
742
		if ( count( $intersection ) > 1 ) {
743
			$this->dieUsage(
744
				"The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
745
				'invalidparammix'
746
			);
747
		}
748
	}
749
750
	/**
751
	 * Die if none of a certain set of parameters is set and not false.
752
	 *
753
	 * @since 1.23
754
	 * @param array $params User provided set of parameters, as from $this->extractRequestParams()
755
	 * @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...
756
	 */
757 View Code Duplication
	public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
758
		$required = func_get_args();
759
		array_shift( $required );
760
		$p = $this->getModulePrefix();
761
762
		$intersection = array_intersect(
763
			array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
764
			$required
765
		);
766
767
		if ( count( $intersection ) == 0 ) {
768
			$this->dieUsage( "At least one of the parameters {$p}" .
769
				implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
770
		}
771
	}
772
773
	/**
774
	 * Callback function used in requireOnlyOneParameter to check whether required parameters are set
775
	 *
776
	 * @param object $x Parameter to check is not null/false
777
	 * @return bool
778
	 */
779
	private function parameterNotEmpty( $x ) {
780
		return !is_null( $x ) && $x !== false;
781
	}
782
783
	/**
784
	 * Get a WikiPage object from a title or pageid param, if possible.
785
	 * Can die, if no param is set or if the title or page id is not valid.
786
	 *
787
	 * @param array $params
788
	 * @param bool|string $load Whether load the object's state from the database:
789
	 *        - false: don't load (if the pageid is given, it will still be loaded)
790
	 *        - 'fromdb': load from a slave database
791
	 *        - 'fromdbmaster': load from the master database
792
	 * @return WikiPage
793
	 */
794
	public function getTitleOrPageId( $params, $load = false ) {
795
		$this->requireOnlyOneParameter( $params, 'title', 'pageid' );
796
797
		$pageObj = null;
798
		if ( isset( $params['title'] ) ) {
799
			$titleObj = Title::newFromText( $params['title'] );
800
			if ( !$titleObj || $titleObj->isExternal() ) {
801
				$this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
802
			}
803
			if ( !$titleObj->canExist() ) {
804
				$this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
805
			}
806
			$pageObj = WikiPage::factory( $titleObj );
0 ignored issues
show
Bug introduced by
It seems like $titleObj defined by \Title::newFromText($params['title']) on line 799 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...
807
			if ( $load !== false ) {
808
				$pageObj->loadPageData( $load );
0 ignored issues
show
Bug introduced by
It seems like $load defined by parameter $load on line 794 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...
809
			}
810
		} elseif ( isset( $params['pageid'] ) ) {
811
			if ( $load === false ) {
812
				$load = 'fromdb';
813
			}
814
			$pageObj = WikiPage::newFromID( $params['pageid'], $load );
0 ignored issues
show
Bug introduced by
It seems like $load defined by parameter $load on line 794 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...
815
			if ( !$pageObj ) {
816
				$this->dieUsageMsg( [ 'nosuchpageid', $params['pageid'] ] );
817
			}
818
		}
819
820
		return $pageObj;
821
	}
822
823
	/**
824
	 * Return true if we're to watch the page, false if not, null if no change.
825
	 * @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
826
	 * @param Title $titleObj The page under consideration
827
	 * @param string $userOption The user option to consider when $watchlist=preferences.
828
	 *    If not set will use watchdefault always and watchcreations if $titleObj doesn't exist.
829
	 * @return bool
830
	 */
831
	protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
832
833
		$userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
834
835
		switch ( $watchlist ) {
836
			case 'watch':
837
				return true;
838
839
			case 'unwatch':
840
				return false;
841
842
			case 'preferences':
843
				# If the user is already watching, don't bother checking
844
				if ( $userWatching ) {
845
					return true;
846
				}
847
				# If no user option was passed, use watchdefault and watchcreations
848
				if ( is_null( $userOption ) ) {
849
					return $this->getUser()->getBoolOption( 'watchdefault' ) ||
850
						$this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
851
				}
852
853
				# Watch the article based on the user preference
854
				return $this->getUser()->getBoolOption( $userOption );
855
856
			case 'nochange':
857
				return $userWatching;
858
859
			default:
860
				return $userWatching;
861
		}
862
	}
863
864
	/**
865
	 * Using the settings determine the value for the given parameter
866
	 *
867
	 * @param string $paramName Parameter name
868
	 * @param array|mixed $paramSettings Default value or an array of settings
869
	 *  using PARAM_* constants.
870
	 * @param bool $parseLimit Parse limit?
871
	 * @return mixed Parameter value
872
	 */
873
	protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
874
		// Some classes may decide to change parameter names
875
		$encParamName = $this->encodeParamName( $paramName );
876
877
		if ( !is_array( $paramSettings ) ) {
878
			$default = $paramSettings;
879
			$multi = false;
880
			$type = gettype( $paramSettings );
881
			$dupes = false;
882
			$deprecated = false;
883
			$required = false;
884
		} else {
885
			$default = isset( $paramSettings[self::PARAM_DFLT] )
886
				? $paramSettings[self::PARAM_DFLT]
887
				: null;
888
			$multi = isset( $paramSettings[self::PARAM_ISMULTI] )
889
				? $paramSettings[self::PARAM_ISMULTI]
890
				: false;
891
			$type = isset( $paramSettings[self::PARAM_TYPE] )
892
				? $paramSettings[self::PARAM_TYPE]
893
				: null;
894
			$dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
895
				? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
896
				: false;
897
			$deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
898
				? $paramSettings[self::PARAM_DEPRECATED]
899
				: false;
900
			$required = isset( $paramSettings[self::PARAM_REQUIRED] )
901
				? $paramSettings[self::PARAM_REQUIRED]
902
				: false;
903
904
			// When type is not given, and no choices, the type is the same as $default
905
			if ( !isset( $type ) ) {
906
				if ( isset( $default ) ) {
907
					$type = gettype( $default );
908
				} else {
909
					$type = 'NULL'; // allow everything
910
				}
911
			}
912
		}
913
914
		if ( $type == 'boolean' ) {
915
			if ( isset( $default ) && $default !== false ) {
916
				// Having a default value of anything other than 'false' is not allowed
917
				ApiBase::dieDebug(
918
					__METHOD__,
919
					"Boolean param $encParamName's default is set to '$default'. " .
920
						'Boolean parameters must default to false.'
921
				);
922
			}
923
924
			$value = $this->getMain()->getCheck( $encParamName );
925
		} elseif ( $type == 'upload' ) {
926
			if ( isset( $default ) ) {
927
				// Having a default value is not allowed
928
				ApiBase::dieDebug(
929
					__METHOD__,
930
					"File upload param $encParamName's default is set to " .
931
						"'$default'. File upload parameters may not have a default." );
932
			}
933
			if ( $multi ) {
934
				ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
935
			}
936
			$value = $this->getMain()->getUpload( $encParamName );
937
			if ( !$value->exists() ) {
938
				// This will get the value without trying to normalize it
939
				// (because trying to normalize a large binary file
940
				// accidentally uploaded as a field fails spectacularly)
941
				$value = $this->getMain()->getRequest()->unsetVal( $encParamName );
942
				if ( $value !== null ) {
943
					$this->dieUsage(
944
						"File upload param $encParamName is not a file upload; " .
945
							'be sure to use multipart/form-data for your POST and include ' .
946
							'a filename in the Content-Disposition header.',
947
						"badupload_{$encParamName}"
948
					);
949
				}
950
			}
951
		} else {
952
			$value = $this->getMain()->getVal( $encParamName, $default );
953
954
			if ( isset( $value ) && $type == 'namespace' ) {
955
				$type = MWNamespace::getValidNamespaces();
956
			}
957 View Code Duplication
			if ( isset( $value ) && $type == 'submodule' ) {
958
				if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
959
					$type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
960
				} else {
961
					$type = $this->getModuleManager()->getNames( $paramName );
962
				}
963
			}
964
		}
965
966
		if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
967
			$value = $this->parseMultiValue(
968
				$encParamName,
969
				$value,
970
				$multi,
971
				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...
972
			);
973
		}
974
975
		// More validation only when choices were not given
976
		// choices were validated in parseMultiValue()
977
		if ( isset( $value ) ) {
978
			if ( !is_array( $type ) ) {
979
				switch ( $type ) {
980
					case 'NULL': // nothing to do
981
						break;
982
					case 'string':
983
					case 'text':
984
					case 'password':
985
						if ( $required && $value === '' ) {
986
							$this->dieUsageMsg( [ 'missingparam', $paramName ] );
987
						}
988
						break;
989
					case 'integer': // Force everything using intval() and optionally validate limits
990
						$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
991
						$max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
992
						$enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
993
							? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
994
995
						if ( is_array( $value ) ) {
996
							$value = array_map( 'intval', $value );
997 View Code Duplication
							if ( !is_null( $min ) || !is_null( $max ) ) {
998
								foreach ( $value as &$v ) {
999
									$this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1000
								}
1001
							}
1002 View Code Duplication
						} else {
1003
							$value = intval( $value );
1004
							if ( !is_null( $min ) || !is_null( $max ) ) {
1005
								$this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1006
							}
1007
						}
1008
						break;
1009
					case 'limit':
1010
						if ( !$parseLimit ) {
1011
							// Don't do any validation whatsoever
1012
							break;
1013
						}
1014
						if ( !isset( $paramSettings[self::PARAM_MAX] )
1015
							|| !isset( $paramSettings[self::PARAM_MAX2] )
1016
						) {
1017
							ApiBase::dieDebug(
1018
								__METHOD__,
1019
								"MAX1 or MAX2 are not defined for the limit $encParamName"
1020
							);
1021
						}
1022
						if ( $multi ) {
1023
							ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1024
						}
1025
						$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
1026
						if ( $value == 'max' ) {
1027
							$value = $this->getMain()->canApiHighLimits()
1028
								? $paramSettings[self::PARAM_MAX2]
1029
								: $paramSettings[self::PARAM_MAX];
1030
							$this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1031
						} else {
1032
							$value = intval( $value );
1033
							$this->validateLimit(
1034
								$paramName,
1035
								$value,
1036
								$min,
1037
								$paramSettings[self::PARAM_MAX],
1038
								$paramSettings[self::PARAM_MAX2]
1039
							);
1040
						}
1041
						break;
1042
					case 'boolean':
1043
						if ( $multi ) {
1044
							ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1045
						}
1046
						break;
1047 View Code Duplication
					case 'timestamp':
1048
						if ( is_array( $value ) ) {
1049
							foreach ( $value as $key => $val ) {
1050
								$value[$key] = $this->validateTimestamp( $val, $encParamName );
1051
							}
1052
						} else {
1053
							$value = $this->validateTimestamp( $value, $encParamName );
1054
						}
1055
						break;
1056 View Code Duplication
					case 'user':
1057
						if ( is_array( $value ) ) {
1058
							foreach ( $value as $key => $val ) {
1059
								$value[$key] = $this->validateUser( $val, $encParamName );
1060
							}
1061
						} else {
1062
							$value = $this->validateUser( $value, $encParamName );
1063
						}
1064
						break;
1065
					case 'upload': // nothing to do
1066
						break;
1067
					case 'tags':
1068
						// If change tagging was requested, check that the tags are valid.
1069
						if ( !is_array( $value ) && !$multi ) {
1070
							$value = [ $value ];
1071
						}
1072
						$tagsStatus = ChangeTags::canAddTagsAccompanyingChange( $value );
1073
						if ( !$tagsStatus->isGood() ) {
1074
							$this->dieStatus( $tagsStatus );
1075
						}
1076
						break;
1077
					default:
1078
						ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1079
				}
1080
			}
1081
1082
			// Throw out duplicates if requested
1083
			if ( !$dupes && is_array( $value ) ) {
1084
				$value = array_unique( $value );
1085
			}
1086
1087
			// Set a warning if a deprecated parameter has been passed
1088
			if ( $deprecated && $value !== false ) {
1089
				$this->setWarning( "The $encParamName parameter has been deprecated." );
1090
1091
				$feature = $encParamName;
1092
				$m = $this;
1093
				while ( !$m->isMain() ) {
1094
					$p = $m->getParent();
1095
					$name = $m->getModuleName();
1096
					$param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1097
					$feature = "{$param}={$name}&{$feature}";
1098
					$m = $p;
1099
				}
1100
				$this->logFeatureUsage( $feature );
1101
			}
1102
		} elseif ( $required ) {
1103
			$this->dieUsageMsg( [ 'missingparam', $paramName ] );
1104
		}
1105
1106
		return $value;
1107
	}
1108
1109
	/**
1110
	 * Return an array of values that were given in a 'a|b|c' notation,
1111
	 * after it optionally validates them against the list allowed values.
1112
	 *
1113
	 * @param string $valueName The name of the parameter (for error
1114
	 *  reporting)
1115
	 * @param mixed $value The value being parsed
1116
	 * @param bool $allowMultiple Can $value contain more than one value
1117
	 *  separated by '|'?
1118
	 * @param string[]|null $allowedValues An array of values to check against. If
1119
	 *  null, all values are accepted.
1120
	 * @return string|string[] (allowMultiple ? an_array_of_values : a_single_value)
1121
	 */
1122
	protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
1123
		if ( trim( $value ) === '' && $allowMultiple ) {
1124
			return [];
1125
		}
1126
1127
		// This is a bit awkward, but we want to avoid calling canApiHighLimits()
1128
		// because it unstubs $wgUser
1129
		$valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
1130
		$sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
1131
			? self::LIMIT_SML2
1132
			: self::LIMIT_SML1;
1133
1134
		if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
1135
			$this->setWarning( "Too many values supplied for parameter '$valueName': " .
1136
				"the limit is $sizeLimit" );
1137
		}
1138
1139
		if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1140
			// Bug 33482 - Allow entries with | in them for non-multiple values
1141
			if ( in_array( $value, $allowedValues, true ) ) {
1142
				return $value;
1143
			}
1144
1145
			$possibleValues = is_array( $allowedValues )
1146
				? "of '" . implode( "', '", $allowedValues ) . "'"
1147
				: '';
1148
			$this->dieUsage(
1149
				"Only one $possibleValues is allowed for parameter '$valueName'",
1150
				"multival_$valueName"
1151
			);
1152
		}
1153
1154
		if ( is_array( $allowedValues ) ) {
1155
			// Check for unknown values
1156
			$unknown = array_diff( $valuesList, $allowedValues );
1157
			if ( count( $unknown ) ) {
1158
				if ( $allowMultiple ) {
1159
					$s = count( $unknown ) > 1 ? 's' : '';
1160
					$vals = implode( ', ', $unknown );
1161
					$this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
1162
				} else {
1163
					$this->dieUsage(
1164
						"Unrecognized value for parameter '$valueName': {$valuesList[0]}",
1165
						"unknown_$valueName"
1166
					);
1167
				}
1168
			}
1169
			// Now throw them out
1170
			$valuesList = array_intersect( $valuesList, $allowedValues );
1171
		}
1172
1173
		return $allowMultiple ? $valuesList : $valuesList[0];
1174
	}
1175
1176
	/**
1177
	 * Validate the value against the minimum and user/bot maximum limits.
1178
	 * Prints usage info on failure.
1179
	 * @param string $paramName Parameter name
1180
	 * @param int $value Parameter value
1181
	 * @param int|null $min Minimum value
1182
	 * @param int|null $max Maximum value for users
1183
	 * @param int $botMax Maximum value for sysops/bots
1184
	 * @param bool $enforceLimits Whether to enforce (die) if value is outside limits
1185
	 */
1186
	protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1187
		$enforceLimits = false
1188
	) {
1189
		if ( !is_null( $min ) && $value < $min ) {
1190
			$msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
1191
			$this->warnOrDie( $msg, $enforceLimits );
1192
			$value = $min;
1193
		}
1194
1195
		// Minimum is always validated, whereas maximum is checked only if not
1196
		// running in internal call mode
1197
		if ( $this->getMain()->isInternalMode() ) {
1198
			return;
1199
		}
1200
1201
		// Optimization: do not check user's bot status unless really needed -- skips db query
1202
		// assumes $botMax >= $max
1203
		if ( !is_null( $max ) && $value > $max ) {
1204
			if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1205 View Code Duplication
				if ( $value > $botMax ) {
1206
					$msg = $this->encodeParamName( $paramName ) .
1207
						" may not be over $botMax (set to $value) for bots or sysops";
1208
					$this->warnOrDie( $msg, $enforceLimits );
1209
					$value = $botMax;
1210
				}
1211 View Code Duplication
			} else {
1212
				$msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
1213
				$this->warnOrDie( $msg, $enforceLimits );
1214
				$value = $max;
1215
			}
1216
		}
1217
	}
1218
1219
	/**
1220
	 * Validate and normalize of parameters of type 'timestamp'
1221
	 * @param string $value Parameter value
1222
	 * @param string $encParamName Parameter name
1223
	 * @return string Validated and normalized parameter
1224
	 */
1225
	protected function validateTimestamp( $value, $encParamName ) {
1226
		// Confusing synonyms for the current time accepted by wfTimestamp()
1227
		// (wfTimestamp() also accepts various non-strings and the string of 14
1228
		// ASCII NUL bytes, but those can't get here)
1229
		if ( !$value ) {
1230
			$this->logFeatureUsage( 'unclear-"now"-timestamp' );
1231
			$this->setWarning(
1232
				"Passing '$value' for timestamp parameter $encParamName has been deprecated." .
1233
					' If for some reason you need to explicitly specify the current time without' .
1234
					' calculating it client-side, use "now".'
1235
			);
1236
			return wfTimestamp( TS_MW );
1237
		}
1238
1239
		// Explicit synonym for the current time
1240
		if ( $value === 'now' ) {
1241
			return wfTimestamp( TS_MW );
1242
		}
1243
1244
		$unixTimestamp = wfTimestamp( TS_UNIX, $value );
1245
		if ( $unixTimestamp === false ) {
1246
			$this->dieUsage(
1247
				"Invalid value '$value' for timestamp parameter $encParamName",
1248
				"badtimestamp_{$encParamName}"
1249
			);
1250
		}
1251
1252
		return wfTimestamp( TS_MW, $unixTimestamp );
1253
	}
1254
1255
	/**
1256
	 * Validate the supplied token.
1257
	 *
1258
	 * @since 1.24
1259
	 * @param string $token Supplied token
1260
	 * @param array $params All supplied parameters for the module
1261
	 * @return bool
1262
	 * @throws MWException
1263
	 */
1264
	final public function validateToken( $token, array $params ) {
1265
		$tokenType = $this->needsToken();
1266
		$salts = ApiQueryTokens::getTokenTypeSalts();
1267
		if ( !isset( $salts[$tokenType] ) ) {
1268
			throw new MWException(
1269
				"Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1270
					'without registering it'
1271
			);
1272
		}
1273
1274
		$tokenObj = ApiQueryTokens::getToken(
1275
			$this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1276
		);
1277
		if ( $tokenObj->match( $token ) ) {
1278
			return true;
1279
		}
1280
1281
		$webUiSalt = $this->getWebUITokenSalt( $params );
1282
		if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1283
			$token,
1284
			$webUiSalt,
0 ignored issues
show
Bug introduced by
It seems like $webUiSalt defined by $this->getWebUITokenSalt($params) on line 1281 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...
1285
			$this->getRequest()
1286
		) ) {
1287
			return true;
1288
		}
1289
1290
		return false;
1291
	}
1292
1293
	/**
1294
	 * Validate and normalize of parameters of type 'user'
1295
	 * @param string $value Parameter value
1296
	 * @param string $encParamName Parameter name
1297
	 * @return string Validated and normalized parameter
1298
	 */
1299
	private function validateUser( $value, $encParamName ) {
1300
		$title = Title::makeTitleSafe( NS_USER, $value );
1301
		if ( $title === null ) {
1302
			$this->dieUsage(
1303
				"Invalid value '$value' for user parameter $encParamName",
1304
				"baduser_{$encParamName}"
1305
			);
1306
		}
1307
1308
		return $title->getText();
1309
	}
1310
1311
	/**@}*/
1312
1313
	/************************************************************************//**
1314
	 * @name   Utility methods
1315
	 * @{
1316
	 */
1317
1318
	/**
1319
	 * Set a watch (or unwatch) based the based on a watchlist parameter.
1320
	 * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
1321
	 * @param Title $titleObj The article's title to change
1322
	 * @param string $userOption The user option to consider when $watch=preferences
1323
	 */
1324
	protected function setWatch( $watch, $titleObj, $userOption = null ) {
1325
		$value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1326
		if ( $value === null ) {
1327
			return;
1328
		}
1329
1330
		WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1331
	}
1332
1333
	/**
1334
	 * Truncate an array to a certain length.
1335
	 * @param array $arr Array to truncate
1336
	 * @param int $limit Maximum length
1337
	 * @return bool True if the array was truncated, false otherwise
1338
	 */
1339
	public static function truncateArray( &$arr, $limit ) {
1340
		$modified = false;
1341
		while ( count( $arr ) > $limit ) {
1342
			array_pop( $arr );
1343
			$modified = true;
1344
		}
1345
1346
		return $modified;
1347
	}
1348
1349
	/**
1350
	 * Gets the user for whom to get the watchlist
1351
	 *
1352
	 * @param array $params
1353
	 * @return User
1354
	 */
1355
	public function getWatchlistUser( $params ) {
1356
		if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1357
			$user = User::newFromName( $params['owner'], false );
1358
			if ( !( $user && $user->getId() ) ) {
1359
				$this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
1360
			}
1361
			$token = $user->getOption( 'watchlisttoken' );
1362
			if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1363
				$this->dieUsage(
1364
					'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
1365
					'bad_wltoken'
1366
				);
1367
			}
1368
		} else {
1369
			if ( !$this->getUser()->isLoggedIn() ) {
1370
				$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
1371
			}
1372
			if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
1373
				$this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
1374
			}
1375
			$user = $this->getUser();
1376
		}
1377
1378
		return $user;
1379
	}
1380
1381
	/**
1382
	 * A subset of wfEscapeWikiText for BC texts
1383
	 *
1384
	 * @since 1.25
1385
	 * @param string|array $v
1386
	 * @return string|array
1387
	 */
1388 View Code Duplication
	private static function escapeWikiText( $v ) {
1389
		if ( is_array( $v ) ) {
1390
			return array_map( 'self::escapeWikiText', $v );
1391
		} else {
1392
			return strtr( $v, [
1393
				'__' => '_&#95;', '{' => '&#123;', '}' => '&#125;',
1394
				'[[Category:' => '[[:Category:',
1395
				'[[File:' => '[[:File:', '[[Image:' => '[[:Image:',
1396
			] );
1397
		}
1398
	}
1399
1400
	/**
1401
	 * Create a Message from a string or array
1402
	 *
1403
	 * A string is used as a message key. An array has the message key as the
1404
	 * first value and message parameters as subsequent values.
1405
	 *
1406
	 * @since 1.25
1407
	 * @param string|array|Message $msg
1408
	 * @param IContextSource $context
1409
	 * @param array $params
1410
	 * @return Message|null
1411
	 */
1412
	public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1413
		if ( is_string( $msg ) ) {
1414
			$msg = wfMessage( $msg );
1415
		} elseif ( is_array( $msg ) ) {
1416
			$msg = call_user_func_array( 'wfMessage', $msg );
1417
		}
1418
		if ( !$msg instanceof Message ) {
1419
			return null;
1420
		}
1421
1422
		$msg->setContext( $context );
1423
		if ( $params ) {
1424
			$msg->params( $params );
1425
		}
1426
1427
		return $msg;
1428
	}
1429
1430
	/**@}*/
1431
1432
	/************************************************************************//**
1433
	 * @name   Warning and error reporting
1434
	 * @{
1435
	 */
1436
1437
	/**
1438
	 * Set warning section for this module. Users should monitor this
1439
	 * section to notice any changes in API. Multiple calls to this
1440
	 * function will result in the warning messages being separated by
1441
	 * newlines
1442
	 * @param string $warning Warning message
1443
	 */
1444
	public function setWarning( $warning ) {
1445
		$msg = new ApiRawMessage( $warning, 'warning' );
1446
		$this->getErrorFormatter()->addWarning( $this->getModuleName(), $msg );
1447
	}
1448
1449
	/**
1450
	 * Adds a warning to the output, else dies
1451
	 *
1452
	 * @param string $msg Message to show as a warning, or error message if dying
1453
	 * @param bool $enforceLimits Whether this is an enforce (die)
1454
	 */
1455
	private function warnOrDie( $msg, $enforceLimits = false ) {
1456
		if ( $enforceLimits ) {
1457
			$this->dieUsage( $msg, 'integeroutofrange' );
1458
		}
1459
1460
		$this->setWarning( $msg );
1461
	}
1462
1463
	/**
1464
	 * Throw a UsageException, which will (if uncaught) call the main module's
1465
	 * error handler and die with an error message.
1466
	 *
1467
	 * @param string $description One-line human-readable description of the
1468
	 *   error condition, e.g., "The API requires a valid action parameter"
1469
	 * @param string $errorCode Brief, arbitrary, stable string to allow easy
1470
	 *   automated identification of the error, e.g., 'unknown_action'
1471
	 * @param int $httpRespCode HTTP response code
1472
	 * @param array|null $extradata Data to add to the "<error>" element; array in ApiResult format
1473
	 * @throws UsageException always
1474
	 */
1475
	public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
1476
		throw new UsageException(
1477
			$description,
1478
			$this->encodeParamName( $errorCode ),
1479
			$httpRespCode,
1480
			$extradata
1481
		);
1482
	}
1483
1484
	/**
1485
	 * Throw a UsageException, which will (if uncaught) call the main module's
1486
	 * error handler and die with an error message including block info.
1487
	 *
1488
	 * @since 1.27
1489
	 * @param Block $block The block used to generate the UsageException
1490
	 * @throws UsageException always
1491
	 */
1492
	public function dieBlocked( Block $block ) {
1493
		// Die using the appropriate message depending on block type
1494
		if ( $block->getType() == Block::TYPE_AUTO ) {
1495
			$this->dieUsage(
1496
				'Your IP address has been blocked automatically, because it was used by a blocked user',
1497
				'autoblocked',
1498
				0,
1499
				[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1500
			);
1501
		} else {
1502
			$this->dieUsage(
1503
				'You have been blocked from editing',
1504
				'blocked',
1505
				0,
1506
				[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1507
			);
1508
		}
1509
	}
1510
1511
	/**
1512
	 * Get error (as code, string) from a Status object.
1513
	 *
1514
	 * @since 1.23
1515
	 * @param Status $status
1516
	 * @param array|null &$extraData Set if extra data from IApiMessage is available (since 1.27)
1517
	 * @return array Array of code and error string
1518
	 * @throws MWException
1519
	 */
1520
	public function getErrorFromStatus( $status, &$extraData = null ) {
1521
		if ( $status->isGood() ) {
1522
			throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
1523
		}
1524
1525
		$errors = $status->getErrorsArray();
0 ignored issues
show
Deprecated Code introduced by
The method Status::getErrorsArray() has been deprecated with message: 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...
1526
		if ( !$errors ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1527
			// No errors? Assume the warnings should be treated as errors
1528
			$errors = $status->getWarningsArray();
0 ignored issues
show
Deprecated Code introduced by
The method Status::getWarningsArray() has been deprecated with message: 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...
1529
		}
1530
		if ( !$errors ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1531
			// Still no errors? Punt
1532
			$errors = [ [ 'unknownerror-nocode' ] ];
1533
		}
1534
1535
		// Cannot use dieUsageMsg() because extensions might return custom
1536
		// error messages.
1537
		if ( $errors[0] instanceof Message ) {
1538
			$msg = $errors[0];
1539
			if ( $msg instanceof IApiMessage ) {
1540
				$extraData = $msg->getApiData();
1541
				$code = $msg->getApiCode();
1542
			} else {
1543
				$code = $msg->getKey();
1544
			}
1545
		} else {
1546
			$code = array_shift( $errors[0] );
1547
			$msg = wfMessage( $code, $errors[0] );
1548
		}
1549
		if ( isset( ApiBase::$messageMap[$code] ) ) {
1550
			// Translate message to code, for backwards compatibility
1551
			$code = ApiBase::$messageMap[$code]['code'];
1552
		}
1553
1554
		return [ $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() ];
1555
	}
1556
1557
	/**
1558
	 * Throw a UsageException based on the errors in the Status object.
1559
	 *
1560
	 * @since 1.22
1561
	 * @param Status $status
1562
	 * @throws UsageException always
1563
	 */
1564
	public function dieStatus( $status ) {
1565
		$extraData = null;
1566
		list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
1567
		$this->dieUsage( $msg, $code, 0, $extraData );
1568
	}
1569
1570
	// @codingStandardsIgnoreStart Allow long lines. Cannot split these.
1571
	/**
1572
	 * Array that maps message keys to error messages. $1 and friends are replaced.
1573
	 */
1574
	public static $messageMap = [
1575
		// This one MUST be present, or dieUsageMsg() will recurse infinitely
1576
		'unknownerror' => [ 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ],
1577
		'unknownerror-nocode' => [ 'code' => 'unknownerror', 'info' => 'Unknown error' ],
1578
1579
		// Messages from Title::getUserPermissionsErrors()
1580
		'ns-specialprotected' => [
1581
			'code' => 'unsupportednamespace',
1582
			'info' => "Pages in the Special namespace can't be edited"
1583
		],
1584
		'protectedinterface' => [
1585
			'code' => 'protectednamespace-interface',
1586
			'info' => "You're not allowed to edit interface messages"
1587
		],
1588
		'namespaceprotected' => [
1589
			'code' => 'protectednamespace',
1590
			'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
1591
		],
1592
		'customcssprotected' => [
1593
			'code' => 'customcssprotected',
1594
			'info' => "You're not allowed to edit custom CSS pages"
1595
		],
1596
		'customjsprotected' => [
1597
			'code' => 'customjsprotected',
1598
			'info' => "You're not allowed to edit custom JavaScript pages"
1599
		],
1600
		'cascadeprotected' => [
1601
			'code' => 'cascadeprotected',
1602
			'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
1603
		],
1604
		'protectedpagetext' => [
1605
			'code' => 'protectedpage',
1606
			'info' => "The \"\$1\" right is required to edit this page"
1607
		],
1608
		'protect-cantedit' => [
1609
			'code' => 'cantedit',
1610
			'info' => "You can't protect this page because you can't edit it"
1611
		],
1612
		'deleteprotected' => [
1613
			'code' => 'cantedit',
1614
			'info' => "You can't delete this page because it has been protected"
1615
		],
1616
		'badaccess-group0' => [
1617
			'code' => 'permissiondenied',
1618
			'info' => 'Permission denied'
1619
		], // Generic permission denied message
1620
		'badaccess-groups' => [
1621
			'code' => 'permissiondenied',
1622
			'info' => 'Permission denied'
1623
		],
1624
		'titleprotected' => [
1625
			'code' => 'protectedtitle',
1626
			'info' => 'This title has been protected from creation'
1627
		],
1628
		'nocreate-loggedin' => [
1629
			'code' => 'cantcreate',
1630
			'info' => "You don't have permission to create new pages"
1631
		],
1632
		'nocreatetext' => [
1633
			'code' => 'cantcreate-anon',
1634
			'info' => "Anonymous users can't create new pages"
1635
		],
1636
		'movenologintext' => [
1637
			'code' => 'cantmove-anon',
1638
			'info' => "Anonymous users can't move pages"
1639
		],
1640
		'movenotallowed' => [
1641
			'code' => 'cantmove',
1642
			'info' => "You don't have permission to move pages"
1643
		],
1644
		'confirmedittext' => [
1645
			'code' => 'confirmemail',
1646
			'info' => 'You must confirm your email address before you can edit'
1647
		],
1648
		'blockedtext' => [
1649
			'code' => 'blocked',
1650
			'info' => 'You have been blocked from editing'
1651
		],
1652
		'autoblockedtext' => [
1653
			'code' => 'autoblocked',
1654
			'info' => 'Your IP address has been blocked automatically, because it was used by a blocked user'
1655
		],
1656
1657
		// Miscellaneous interface messages
1658
		'actionthrottledtext' => [
1659
			'code' => 'ratelimited',
1660
			'info' => "You've exceeded your rate limit. Please wait some time and try again"
1661
		],
1662
		'alreadyrolled' => [
1663
			'code' => 'alreadyrolled',
1664
			'info' => 'The page you tried to rollback was already rolled back'
1665
		],
1666
		'cantrollback' => [
1667
			'code' => 'onlyauthor',
1668
			'info' => 'The page you tried to rollback only has one author'
1669
		],
1670
		'readonlytext' => [
1671
			'code' => 'readonly',
1672
			'info' => 'The wiki is currently in read-only mode'
1673
		],
1674
		'sessionfailure' => [
1675
			'code' => 'badtoken',
1676
			'info' => 'Invalid token' ],
1677
		'cannotdelete' => [
1678
			'code' => 'cantdelete',
1679
			'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
1680
		],
1681
		'notanarticle' => [
1682
			'code' => 'missingtitle',
1683
			'info' => "The page you requested doesn't exist"
1684
		],
1685
		'selfmove' => [ 'code' => 'selfmove', 'info' => "Can't move a page to itself"
1686
		],
1687
		'immobile_namespace' => [
1688
			'code' => 'immobilenamespace',
1689
			'info' => 'You tried to move pages from or to a namespace that is protected from moving'
1690
		],
1691
		'articleexists' => [
1692
			'code' => 'articleexists',
1693
			'info' => 'The destination article already exists and is not a redirect to the source article'
1694
		],
1695
		'protectedpage' => [
1696
			'code' => 'protectedpage',
1697
			'info' => "You don't have permission to perform this move"
1698
		],
1699
		'hookaborted' => [
1700
			'code' => 'hookaborted',
1701
			'info' => 'The modification you tried to make was aborted by an extension hook'
1702
		],
1703
		'cantmove-titleprotected' => [
1704
			'code' => 'protectedtitle',
1705
			'info' => 'The destination article has been protected from creation'
1706
		],
1707
		'imagenocrossnamespace' => [
1708
			'code' => 'nonfilenamespace',
1709
			'info' => "Can't move a file to a non-file namespace"
1710
		],
1711
		'imagetypemismatch' => [
1712
			'code' => 'filetypemismatch',
1713
			'info' => "The new file extension doesn't match its type"
1714
		],
1715
		// 'badarticleerror' => shouldn't happen
1716
		// 'badtitletext' => shouldn't happen
1717
		'ip_range_invalid' => [ 'code' => 'invalidrange', 'info' => 'Invalid IP range' ],
1718
		'range_block_disabled' => [
1719
			'code' => 'rangedisabled',
1720
			'info' => 'Blocking IP ranges has been disabled'
1721
		],
1722
		'nosuchusershort' => [
1723
			'code' => 'nosuchuser',
1724
			'info' => "The user you specified doesn't exist"
1725
		],
1726
		'badipaddress' => [ 'code' => 'invalidip', 'info' => 'Invalid IP address specified' ],
1727
		'ipb_expiry_invalid' => [ 'code' => 'invalidexpiry', 'info' => 'Invalid expiry time' ],
1728
		'ipb_already_blocked' => [
1729
			'code' => 'alreadyblocked',
1730
			'info' => 'The user you tried to block was already blocked'
1731
		],
1732
		'ipb_blocked_as_range' => [
1733
			'code' => 'blockedasrange',
1734
			'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."
1735
		],
1736
		'ipb_cant_unblock' => [
1737
			'code' => 'cantunblock',
1738
			'info' => 'The block you specified was not found. It may have been unblocked already'
1739
		],
1740
		'mailnologin' => [
1741
			'code' => 'cantsend',
1742
			'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'
1743
		],
1744
		'ipbblocked' => [
1745
			'code' => 'ipbblocked',
1746
			'info' => 'You cannot block or unblock users while you are yourself blocked'
1747
		],
1748
		'ipbnounblockself' => [
1749
			'code' => 'ipbnounblockself',
1750
			'info' => 'You are not allowed to unblock yourself'
1751
		],
1752
		'usermaildisabled' => [
1753
			'code' => 'usermaildisabled',
1754
			'info' => 'User email has been disabled'
1755
		],
1756
		'blockedemailuser' => [
1757
			'code' => 'blockedfrommail',
1758
			'info' => 'You have been blocked from sending email'
1759
		],
1760
		'notarget' => [
1761
			'code' => 'notarget',
1762
			'info' => 'You have not specified a valid target for this action'
1763
		],
1764
		'noemail' => [
1765
			'code' => 'noemail',
1766
			'info' => 'The user has not specified a valid email address, or has chosen not to receive email from other users'
1767
		],
1768
		'rcpatroldisabled' => [
1769
			'code' => 'patroldisabled',
1770
			'info' => 'Patrolling is disabled on this wiki'
1771
		],
1772
		'markedaspatrollederror-noautopatrol' => [
1773
			'code' => 'noautopatrol',
1774
			'info' => "You don't have permission to patrol your own changes"
1775
		],
1776
		'delete-toobig' => [
1777
			'code' => 'bigdelete',
1778
			'info' => "You can't delete this page because it has more than \$1 revisions"
1779
		],
1780
		'movenotallowedfile' => [
1781
			'code' => 'cantmovefile',
1782
			'info' => "You don't have permission to move files"
1783
		],
1784
		'userrights-no-interwiki' => [
1785
			'code' => 'nointerwikiuserrights',
1786
			'info' => "You don't have permission to change user rights on other wikis"
1787
		],
1788
		'userrights-nodatabase' => [
1789
			'code' => 'nosuchdatabase',
1790
			'info' => "Database \"\$1\" does not exist or is not local"
1791
		],
1792
		'nouserspecified' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1793
		'noname' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1794
		'summaryrequired' => [ 'code' => 'summaryrequired', 'info' => 'Summary required' ],
1795
		'import-rootpage-invalid' => [
1796
			'code' => 'import-rootpage-invalid',
1797
			'info' => 'Root page is an invalid title'
1798
		],
1799
		'import-rootpage-nosubpage' => [
1800
			'code' => 'import-rootpage-nosubpage',
1801
			'info' => 'Namespace "$1" of the root page does not allow subpages'
1802
		],
1803
1804
		// API-specific messages
1805
		'readrequired' => [
1806
			'code' => 'readapidenied',
1807
			'info' => 'You need read permission to use this module'
1808
		],
1809
		'writedisabled' => [
1810
			'code' => 'noapiwrite',
1811
			'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"
1812
		],
1813
		'writerequired' => [
1814
			'code' => 'writeapidenied',
1815
			'info' => "You're not allowed to edit this wiki through the API"
1816
		],
1817
		'missingparam' => [ 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ],
1818
		'invalidtitle' => [ 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ],
1819
		'nosuchpageid' => [ 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ],
1820
		'nosuchrevid' => [ 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ],
1821
		'nosuchuser' => [ 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ],
1822
		'invaliduser' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1823
		'invalidexpiry' => [ 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ],
1824
		'pastexpiry' => [ 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ],
1825
		'create-titleexists' => [
1826
			'code' => 'create-titleexists',
1827
			'info' => "Existing titles can't be protected with 'create'"
1828
		],
1829
		'missingtitle-createonly' => [
1830
			'code' => 'missingtitle-createonly',
1831
			'info' => "Missing titles can only be protected with 'create'"
1832
		],
1833
		'cantblock' => [ 'code' => 'cantblock',
1834
			'info' => "You don't have permission to block users"
1835
		],
1836
		'canthide' => [
1837
			'code' => 'canthide',
1838
			'info' => "You don't have permission to hide user names from the block log"
1839
		],
1840
		'cantblock-email' => [
1841
			'code' => 'cantblock-email',
1842
			'info' => "You don't have permission to block users from sending email through the wiki"
1843
		],
1844
		'unblock-notarget' => [
1845
			'code' => 'notarget',
1846
			'info' => 'Either the id or the user parameter must be set'
1847
		],
1848
		'unblock-idanduser' => [
1849
			'code' => 'idanduser',
1850
			'info' => "The id and user parameters can't be used together"
1851
		],
1852
		'cantunblock' => [
1853
			'code' => 'permissiondenied',
1854
			'info' => "You don't have permission to unblock users"
1855
		],
1856
		'cannotundelete' => [
1857
			'code' => 'cantundelete',
1858
			'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
1859
		],
1860
		'permdenied-undelete' => [
1861
			'code' => 'permissiondenied',
1862
			'info' => "You don't have permission to restore deleted revisions"
1863
		],
1864
		'createonly-exists' => [
1865
			'code' => 'articleexists',
1866
			'info' => 'The article you tried to create has been created already'
1867
		],
1868
		'nocreate-missing' => [
1869
			'code' => 'missingtitle',
1870
			'info' => "The article you tried to edit doesn't exist"
1871
		],
1872
		'cantchangecontentmodel' => [
1873
			'code' => 'cantchangecontentmodel',
1874
			'info' => "You don't have permission to change the content model of a page"
1875
		],
1876
		'nosuchrcid' => [
1877
			'code' => 'nosuchrcid',
1878
			'info' => "There is no change with rcid \"\$1\""
1879
		],
1880
		'nosuchlogid' => [
1881
			'code' => 'nosuchlogid',
1882
			'info' => "There is no log entry with ID \"\$1\""
1883
		],
1884
		'protect-invalidaction' => [
1885
			'code' => 'protect-invalidaction',
1886
			'info' => "Invalid protection type \"\$1\""
1887
		],
1888
		'protect-invalidlevel' => [
1889
			'code' => 'protect-invalidlevel',
1890
			'info' => "Invalid protection level \"\$1\""
1891
		],
1892
		'toofewexpiries' => [
1893
			'code' => 'toofewexpiries',
1894
			'info' => "\$1 expiry timestamps were provided where \$2 were needed"
1895
		],
1896
		'cantimport' => [
1897
			'code' => 'cantimport',
1898
			'info' => "You don't have permission to import pages"
1899
		],
1900
		'cantimport-upload' => [
1901
			'code' => 'cantimport-upload',
1902
			'info' => "You don't have permission to import uploaded pages"
1903
		],
1904
		'importnofile' => [ 'code' => 'nofile', 'info' => "You didn't upload a file" ],
1905
		'importuploaderrorsize' => [
1906
			'code' => 'filetoobig',
1907
			'info' => 'The file you uploaded is bigger than the maximum upload size'
1908
		],
1909
		'importuploaderrorpartial' => [
1910
			'code' => 'partialupload',
1911
			'info' => 'The file was only partially uploaded'
1912
		],
1913
		'importuploaderrortemp' => [
1914
			'code' => 'notempdir',
1915
			'info' => 'The temporary upload directory is missing'
1916
		],
1917
		'importcantopen' => [
1918
			'code' => 'cantopenfile',
1919
			'info' => "Couldn't open the uploaded file"
1920
		],
1921
		'import-noarticle' => [
1922
			'code' => 'badinterwiki',
1923
			'info' => 'Invalid interwiki title specified'
1924
		],
1925
		'importbadinterwiki' => [
1926
			'code' => 'badinterwiki',
1927
			'info' => 'Invalid interwiki title specified'
1928
		],
1929
		'import-unknownerror' => [
1930
			'code' => 'import-unknownerror',
1931
			'info' => "Unknown error on import: \"\$1\""
1932
		],
1933
		'cantoverwrite-sharedfile' => [
1934
			'code' => 'cantoverwrite-sharedfile',
1935
			'info' => 'The target file exists on a shared repository and you do not have permission to override it'
1936
		],
1937
		'sharedfile-exists' => [
1938
			'code' => 'fileexists-sharedrepo-perm',
1939
			'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
1940
		],
1941
		'mustbeposted' => [
1942
			'code' => 'mustbeposted',
1943
			'info' => "The \$1 module requires a POST request"
1944
		],
1945
		'show' => [
1946
			'code' => 'show',
1947
			'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
1948
		],
1949
		'specialpage-cantexecute' => [
1950
			'code' => 'specialpage-cantexecute',
1951
			'info' => "You don't have permission to view the results of this special page"
1952
		],
1953
		'invalidoldimage' => [
1954
			'code' => 'invalidoldimage',
1955
			'info' => 'The oldimage parameter has invalid format'
1956
		],
1957
		'nodeleteablefile' => [
1958
			'code' => 'nodeleteablefile',
1959
			'info' => 'No such old version of the file'
1960
		],
1961
		'fileexists-forbidden' => [
1962
			'code' => 'fileexists-forbidden',
1963
			'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
1964
		],
1965
		'fileexists-shared-forbidden' => [
1966
			'code' => 'fileexists-shared-forbidden',
1967
			'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
1968
		],
1969
		'filerevert-badversion' => [
1970
			'code' => 'filerevert-badversion',
1971
			'info' => 'There is no previous local version of this file with the provided timestamp.'
1972
		],
1973
1974
		// ApiEditPage messages
1975
		'noimageredirect-anon' => [
1976
			'code' => 'noimageredirect-anon',
1977
			'info' => "Anonymous users can't create image redirects"
1978
		],
1979
		'noimageredirect-logged' => [
1980
			'code' => 'noimageredirect',
1981
			'info' => "You don't have permission to create image redirects"
1982
		],
1983
		'spamdetected' => [
1984
			'code' => 'spamdetected',
1985
			'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
1986
		],
1987
		'contenttoobig' => [
1988
			'code' => 'contenttoobig',
1989
			'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
1990
		],
1991
		'noedit-anon' => [ 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ],
1992
		'noedit' => [ 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ],
1993
		'wasdeleted' => [
1994
			'code' => 'pagedeleted',
1995
			'info' => 'The page has been deleted since you fetched its timestamp'
1996
		],
1997
		'blankpage' => [
1998
			'code' => 'emptypage',
1999
			'info' => 'Creating new, empty pages is not allowed'
2000
		],
2001
		'editconflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
2002
		'hashcheckfailed' => [ 'code' => 'badmd5', 'info' => 'The supplied MD5 hash was incorrect' ],
2003
		'missingtext' => [
2004
			'code' => 'notext',
2005
			'info' => 'One of the text, appendtext, prependtext and undo parameters must be set'
2006
		],
2007
		'emptynewsection' => [
2008
			'code' => 'emptynewsection',
2009
			'info' => 'Creating empty new sections is not possible.'
2010
		],
2011
		'revwrongpage' => [
2012
			'code' => 'revwrongpage',
2013
			'info' => "r\$1 is not a revision of \"\$2\""
2014
		],
2015
		'undo-failure' => [
2016
			'code' => 'undofailure',
2017
			'info' => 'Undo failed due to conflicting intermediate edits'
2018
		],
2019
		'content-not-allowed-here' => [
2020
			'code' => 'contentnotallowedhere',
2021
			'info' => 'Content model "$1" is not allowed at title "$2"'
2022
		],
2023
2024
		// Messages from WikiPage::doEit(]
2025
		'edit-hook-aborted' => [
2026
			'code' => 'edit-hook-aborted',
2027
			'info' => 'Your edit was aborted by an ArticleSave hook'
2028
		],
2029
		'edit-gone-missing' => [
2030
			'code' => 'edit-gone-missing',
2031
			'info' => "The page you tried to edit doesn't seem to exist anymore"
2032
		],
2033
		'edit-conflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
2034
		'edit-already-exists' => [
2035
			'code' => 'edit-already-exists',
2036
			'info' => 'It seems the page you tried to create already exist'
2037
		],
2038
2039
		// uploadMsgs
2040
		'invalid-file-key' => [ 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ],
2041
		'nouploadmodule' => [ 'code' => 'nouploadmodule', 'info' => 'No upload module set' ],
2042
		'uploaddisabled' => [
2043
			'code' => 'uploaddisabled',
2044
			'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
2045
		],
2046
		'copyuploaddisabled' => [
2047
			'code' => 'copyuploaddisabled',
2048
			'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
2049
		],
2050
		'copyuploadbaddomain' => [
2051
			'code' => 'copyuploadbaddomain',
2052
			'info' => 'Uploads by URL are not allowed from this domain.'
2053
		],
2054
		'copyuploadbadurl' => [
2055
			'code' => 'copyuploadbadurl',
2056
			'info' => 'Upload not allowed from this URL.'
2057
		],
2058
2059
		'filename-tooshort' => [
2060
			'code' => 'filename-tooshort',
2061
			'info' => 'The filename is too short'
2062
		],
2063
		'filename-toolong' => [ 'code' => 'filename-toolong', 'info' => 'The filename is too long' ],
2064
		'illegal-filename' => [
2065
			'code' => 'illegal-filename',
2066
			'info' => 'The filename is not allowed'
2067
		],
2068
		'filetype-missing' => [
2069
			'code' => 'filetype-missing',
2070
			'info' => 'The file is missing an extension'
2071
		],
2072
2073
		'mustbeloggedin' => [ 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' ]
2074
	];
2075
	// @codingStandardsIgnoreEnd
2076
2077
	/**
2078
	 * Helper function for readonly errors
2079
	 *
2080
	 * @throws UsageException always
2081
	 */
2082
	public function dieReadOnly() {
2083
		$parsed = $this->parseMsg( [ 'readonlytext' ] );
2084
		$this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0,
2085
			[ 'readonlyreason' => wfReadOnlyReason() ] );
2086
	}
2087
2088
	/**
2089
	 * Output the error message related to a certain array
2090
	 * @param array|string $error Element of a getUserPermissionsErrors()-style array
2091
	 * @throws UsageException always
2092
	 */
2093
	public function dieUsageMsg( $error ) {
2094
		# most of the time we send a 1 element, so we might as well send it as
2095
		# a string and make this an array here.
2096
		if ( is_string( $error ) ) {
2097
			$error = [ $error ];
2098
		}
2099
		$parsed = $this->parseMsg( $error );
2100
		$extraData = isset( $parsed['data'] ) ? $parsed['data'] : null;
2101
		$this->dieUsage( $parsed['info'], $parsed['code'], 0, $extraData );
2102
	}
2103
2104
	/**
2105
	 * Will only set a warning instead of failing if the global $wgDebugAPI
2106
	 * is set to true. Otherwise behaves exactly as dieUsageMsg().
2107
	 * @param array|string $error Element of a getUserPermissionsErrors()-style array
2108
	 * @throws UsageException
2109
	 * @since 1.21
2110
	 */
2111
	public function dieUsageMsgOrDebug( $error ) {
2112
		if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2113
			$this->dieUsageMsg( $error );
2114
		}
2115
2116
		if ( is_string( $error ) ) {
2117
			$error = [ $error ];
2118
		}
2119
		$parsed = $this->parseMsg( $error );
2120
		$this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
2121
	}
2122
2123
	/**
2124
	 * Die with the $prefix.'badcontinue' error. This call is common enough to
2125
	 * make it into the base method.
2126
	 * @param bool $condition Will only die if this value is true
2127
	 * @throws UsageException
2128
	 * @since 1.21
2129
	 */
2130
	protected function dieContinueUsageIf( $condition ) {
2131
		if ( $condition ) {
2132
			$this->dieUsage(
2133
				'Invalid continue param. You should pass the original value returned by the previous query',
2134
				'badcontinue' );
2135
		}
2136
	}
2137
2138
	/**
2139
	 * Return the error message related to a certain array
2140
	 * @param array $error Element of a getUserPermissionsErrors()-style array
2141
	 * @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...
2142
	 */
2143
	public function parseMsg( $error ) {
2144
		$error = (array)$error; // It seems strings sometimes make their way in here
2145
		$key = array_shift( $error );
2146
2147
		// Check whether the error array was nested
2148
		// array( array( <code>, <params> ), array( <another_code>, <params> ) )
2149
		if ( is_array( $key ) ) {
2150
			$error = $key;
2151
			$key = array_shift( $error );
2152
		}
2153
2154
		if ( $key instanceof IApiMessage ) {
2155
			return [
2156
				'code' => $key->getApiCode(),
2157
				'info' => $key->inLanguage( 'en' )->useDatabase( false )->text(),
2158
				'data' => $key->getApiData()
2159
			];
2160
		}
2161
2162
		if ( isset( self::$messageMap[$key] ) ) {
2163
			return [
2164
				'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
2165
				'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
2166
			];
2167
		}
2168
2169
		// If the key isn't present, throw an "unknown error"
2170
		return $this->parseMsg( [ 'unknownerror', $key ] );
2171
	}
2172
2173
	/**
2174
	 * Internal code errors should be reported with this method
2175
	 * @param string $method Method or function name
2176
	 * @param string $message Error message
2177
	 * @throws MWException always
2178
	 */
2179
	protected static function dieDebug( $method, $message ) {
2180
		throw new MWException( "Internal error in $method: $message" );
2181
	}
2182
2183
	/**
2184
	 * Write logging information for API features to a debug log, for usage
2185
	 * analysis.
2186
	 * @param string $feature Feature being used.
2187
	 */
2188
	protected function logFeatureUsage( $feature ) {
2189
		$request = $this->getRequest();
2190
		$s = '"' . addslashes( $feature ) . '"' .
2191
			' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
2192
			' "' . $request->getIP() . '"' .
2193
			' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
2194
			' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
2195
		wfDebugLog( 'api-feature-usage', $s, 'private' );
2196
	}
2197
2198
	/**@}*/
2199
2200
	/************************************************************************//**
2201
	 * @name   Help message generation
2202
	 * @{
2203
	 */
2204
2205
	/**
2206
	 * Return the description message.
2207
	 *
2208
	 * @return string|array|Message
2209
	 */
2210
	protected function getDescriptionMessage() {
2211
		return "apihelp-{$this->getModulePath()}-description";
2212
	}
2213
2214
	/**
2215
	 * Get final module description, after hooks have had a chance to tweak it as
2216
	 * needed.
2217
	 *
2218
	 * @since 1.25, returns Message[] rather than string[]
2219
	 * @return Message[]
2220
	 */
2221
	public function getFinalDescription() {
2222
		$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...
2223
		Hooks::run( 'APIGetDescription', [ &$this, &$desc ] );
2224
		$desc = self::escapeWikiText( $desc );
2225
		if ( is_array( $desc ) ) {
2226
			$desc = implode( "\n", $desc );
2227
		} else {
2228
			$desc = (string)$desc;
2229
		}
2230
2231
		$msg = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
2232
			$this->getModulePrefix(),
2233
			$this->getModuleName(),
2234
			$this->getModulePath(),
2235
		] );
2236
		if ( !$msg->exists() ) {
2237
			$msg = $this->msg( 'api-help-fallback-description', $desc );
2238
		}
2239
		$msgs = [ $msg ];
2240
2241
		Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2242
2243
		return $msgs;
2244
	}
2245
2246
	/**
2247
	 * Get final list of parameters, after hooks have had a chance to
2248
	 * tweak it as needed.
2249
	 *
2250
	 * @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
2251
	 * @return array|bool False on no parameters
2252
	 * @since 1.21 $flags param added
2253
	 */
2254
	public function getFinalParams( $flags = 0 ) {
2255
		$params = $this->getAllowedParams( $flags );
2256
		if ( !$params ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2257
			$params = [];
2258
		}
2259
2260
		if ( $this->needsToken() ) {
2261
			$params['token'] = [
2262
				ApiBase::PARAM_TYPE => 'string',
2263
				ApiBase::PARAM_REQUIRED => true,
2264
				ApiBase::PARAM_HELP_MSG => [
2265
					'api-help-param-token',
2266
					$this->needsToken(),
2267
				],
2268
			] + ( isset( $params['token'] ) ? $params['token'] : [] );
2269
		}
2270
2271
		Hooks::run( 'APIGetAllowedParams', [ &$this, &$params, $flags ] );
2272
2273
		return $params;
2274
	}
2275
2276
	/**
2277
	 * Get final parameter descriptions, after hooks have had a chance to tweak it as
2278
	 * needed.
2279
	 *
2280
	 * @since 1.25, returns array of Message[] rather than array of string[]
2281
	 * @return array Keys are parameter names, values are arrays of Message objects
2282
	 */
2283
	public function getFinalParamDescription() {
2284
		$prefix = $this->getModulePrefix();
2285
		$name = $this->getModuleName();
2286
		$path = $this->getModulePath();
2287
2288
		$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...
2289
		Hooks::run( 'APIGetParamDescription', [ &$this, &$desc ] );
2290
2291
		if ( !$desc ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $desc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2695
			$paramsDescription = $this->getFinalParamDescription();
2696
			$msg = '';
2697
			$paramPrefix = "\n" . str_repeat( ' ', 24 );
2698
			$descWordwrap = "\n" . str_repeat( ' ', 28 );
2699
			foreach ( $params as $paramName => $paramSettings ) {
2700
				$desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
2701
				if ( is_array( $desc ) ) {
2702
					$desc = implode( $paramPrefix, $desc );
2703
				}
2704
2705
				// handle shorthand
2706
				if ( !is_array( $paramSettings ) ) {
2707
					$paramSettings = [
2708
						self::PARAM_DFLT => $paramSettings,
2709
					];
2710
				}
2711
2712
				// handle missing type
2713 View Code Duplication
				if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
2714
					$dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
2715
						? $paramSettings[ApiBase::PARAM_DFLT]
2716
						: null;
2717
					if ( is_bool( $dflt ) ) {
2718
						$paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
2719
					} elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
2720
						$paramSettings[ApiBase::PARAM_TYPE] = 'string';
2721
					} elseif ( is_int( $dflt ) ) {
2722
						$paramSettings[ApiBase::PARAM_TYPE] = 'integer';
2723
					}
2724
				}
2725
2726
				if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
2727
					&& $paramSettings[self::PARAM_DEPRECATED]
2728
				) {
2729
					$desc = "DEPRECATED! $desc";
2730
				}
2731
2732
				if ( isset( $paramSettings[self::PARAM_REQUIRED] )
2733
					&& $paramSettings[self::PARAM_REQUIRED]
2734
				) {
2735
					$desc .= $paramPrefix . 'This parameter is required';
2736
				}
2737
2738
				$type = isset( $paramSettings[self::PARAM_TYPE] )
2739
					? $paramSettings[self::PARAM_TYPE]
2740
					: null;
2741
				if ( isset( $type ) ) {
2742
					$hintPipeSeparated = true;
2743
					$multi = isset( $paramSettings[self::PARAM_ISMULTI] )
2744
						? $paramSettings[self::PARAM_ISMULTI]
2745
						: false;
2746
					if ( $multi ) {
2747
						$prompt = 'Values (separate with \'|\'): ';
2748
					} else {
2749
						$prompt = 'One value: ';
2750
					}
2751
2752 View Code Duplication
					if ( $type === 'submodule' ) {
2753
						if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
2754
							$type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
2755
						} else {
2756
							$type = $this->getModuleManager()->getNames( $paramName );
2757
						}
2758
						sort( $type );
2759
					}
2760
					if ( is_array( $type ) ) {
2761
						$choices = [];
2762
						$nothingPrompt = '';
2763
						foreach ( $type as $t ) {
2764
							if ( $t === '' ) {
2765
								$nothingPrompt = 'Can be empty, or ';
2766
							} else {
2767
								$choices[] = $t;
2768
							}
2769
						}
2770
						$desc .= $paramPrefix . $nothingPrompt . $prompt;
2771
						$choicesstring = implode( ', ', $choices );
2772
						$desc .= wordwrap( $choicesstring, 100, $descWordwrap );
2773
						$hintPipeSeparated = false;
2774
					} else {
2775
						switch ( $type ) {
2776
							case 'namespace':
2777
								// Special handling because namespaces are
2778
								// type-limited, yet they are not given
2779
								$desc .= $paramPrefix . $prompt;
2780
								$desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
2781
									100, $descWordwrap );
2782
								$hintPipeSeparated = false;
2783
								break;
2784
							case 'limit':
2785
								$desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
2786
								if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
2787
									$desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
2788
								}
2789
								$desc .= ' allowed';
2790
								break;
2791
							case 'integer':
2792
								$s = $multi ? 's' : '';
2793
								$hasMin = isset( $paramSettings[self::PARAM_MIN] );
2794
								$hasMax = isset( $paramSettings[self::PARAM_MAX] );
2795
								if ( $hasMin || $hasMax ) {
2796
									if ( !$hasMax ) {
2797
										$intRangeStr = "The value$s must be no less than " .
2798
											"{$paramSettings[self::PARAM_MIN]}";
2799
									} elseif ( !$hasMin ) {
2800
										$intRangeStr = "The value$s must be no more than " .
2801
											"{$paramSettings[self::PARAM_MAX]}";
2802
									} else {
2803
										$intRangeStr = "The value$s must be between " .
2804
											"{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
2805
									}
2806
2807
									$desc .= $paramPrefix . $intRangeStr;
2808
								}
2809
								break;
2810
							case 'upload':
2811
								$desc .= $paramPrefix . 'Must be posted as a file upload using multipart/form-data';
2812
								break;
2813
						}
2814
					}
2815
2816
					if ( $multi ) {
2817
						if ( $hintPipeSeparated ) {
2818
							$desc .= $paramPrefix . "Separate values with '|'";
2819
						}
2820
2821
						$isArray = is_array( $type );
2822
						if ( !$isArray
2823
							|| $isArray && count( $type ) > self::LIMIT_SML1
2824
						) {
2825
							$desc .= $paramPrefix . 'Maximum number of values ' .
2826
								self::LIMIT_SML1 . ' (' . self::LIMIT_SML2 . ' for bots)';
2827
						}
2828
					}
2829
				}
2830
2831
				$default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
2832
				if ( !is_null( $default ) && $default !== false ) {
2833
					$desc .= $paramPrefix . "Default: $default";
2834
				}
2835
2836
				$msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
2837
			}
2838
2839
			return $msg;
2840
		}
2841
2842
		return false;
2843
	}
2844
2845
	/**
2846
	 * @deprecated since 1.25, always returns empty string
2847
	 * @param IDatabase|bool $db
2848
	 * @return string
2849
	 */
2850
	public function getModuleProfileName( $db = false ) {
2851
		wfDeprecated( __METHOD__, '1.25' );
2852
		return '';
2853
	}
2854
2855
	/**
2856
	 * @deprecated since 1.25
2857
	 */
2858
	public function profileIn() {
2859
		// No wfDeprecated() yet because extensions call this and might need to
2860
		// keep doing so for BC.
2861
	}
2862
2863
	/**
2864
	 * @deprecated since 1.25
2865
	 */
2866
	public function profileOut() {
2867
		// No wfDeprecated() yet because extensions call this and might need to
2868
		// keep doing so for BC.
2869
	}
2870
2871
	/**
2872
	 * @deprecated since 1.25
2873
	 */
2874
	public function safeProfileOut() {
2875
		wfDeprecated( __METHOD__, '1.25' );
2876
	}
2877
2878
	/**
2879
	 * @deprecated since 1.25, always returns 0
2880
	 * @return float
2881
	 */
2882
	public function getProfileTime() {
2883
		wfDeprecated( __METHOD__, '1.25' );
2884
		return 0;
2885
	}
2886
2887
	/**
2888
	 * @deprecated since 1.25
2889
	 */
2890
	public function profileDBIn() {
2891
		wfDeprecated( __METHOD__, '1.25' );
2892
	}
2893
2894
	/**
2895
	 * @deprecated since 1.25
2896
	 */
2897
	public function profileDBOut() {
2898
		wfDeprecated( __METHOD__, '1.25' );
2899
	}
2900
2901
	/**
2902
	 * @deprecated since 1.25, always returns 0
2903
	 * @return float
2904
	 */
2905
	public function getProfileDBTime() {
2906
		wfDeprecated( __METHOD__, '1.25' );
2907
		return 0;
2908
	}
2909
2910
	/**
2911
	 * Get the result data array (read-only)
2912
	 * @deprecated since 1.25, use $this->getResult() methods instead
2913
	 * @return array
2914
	 */
2915
	public function getResultData() {
2916
		wfDeprecated( __METHOD__, '1.25' );
2917
		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...
2918
	}
2919
2920
	/**
2921
	 * Call wfTransactionalTimeLimit() if this request was POSTed
2922
	 * @since 1.26
2923
	 */
2924
	protected function useTransactionalTimeLimit() {
2925
		if ( $this->getRequest()->wasPosted() ) {
2926
			wfTransactionalTimeLimit();
2927
		}
2928
	}
2929
2930
	/**@}*/
2931
}
2932
2933
/**
2934
 * For really cool vim folding this needs to be at the end:
2935
 * vim: foldmarker=@{,@} foldmethod=marker
2936
 */
2937