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; |
|
|
|
|
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(); |
280
|
|
|
if ( $examples ) { |
281
|
|
|
if ( !is_array( $examples ) ) { |
282
|
|
|
$examples = [ $examples ]; |
283
|
|
|
} elseif ( $examples && ( count( $examples ) & 1 ) == 0 && |
284
|
|
|
array_keys( $examples ) === range( 0, count( $examples ) - 1 ) && |
285
|
|
|
!preg_match( '/^\s*api\.php\?/', $examples[0] ) |
286
|
|
|
) { |
287
|
|
|
// Fix up the ugly "even numbered elements are description, odd |
288
|
|
|
// numbered elemts are the link" format (see doc for self::getExamples) |
289
|
|
|
$tmp = []; |
290
|
|
|
$examplesCount = count( $examples ); |
291
|
|
|
for ( $i = 0; $i < $examplesCount; $i += 2 ) { |
292
|
|
|
$tmp[$examples[$i + 1]] = $examples[$i]; |
293
|
|
|
} |
294
|
|
|
$examples = $tmp; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
foreach ( $examples as $k => $v ) { |
298
|
|
|
if ( is_numeric( $k ) ) { |
299
|
|
|
$qs = $v; |
300
|
|
|
$msg = ''; |
301
|
|
|
} else { |
302
|
|
|
$qs = $k; |
303
|
|
|
$msg = self::escapeWikiText( $v ); |
304
|
|
|
if ( is_array( $msg ) ) { |
305
|
|
|
$msg = implode( ' ', $msg ); |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
$qs = preg_replace( '/^\s*api\.php\?/', '', $qs ); |
310
|
|
|
$ret[$qs] = $this->msg( 'api-help-fallback-example', [ $msg ] ); |
311
|
|
|
} |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
return $ret; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Return links to more detailed help pages about the module. |
319
|
|
|
* @since 1.25, returning boolean false is deprecated |
320
|
|
|
* @return string|array |
321
|
|
|
*/ |
322
|
|
|
public function getHelpUrls() { |
323
|
|
|
return []; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Returns an array of allowed parameters (parameter name) => (default |
328
|
|
|
* value) or (parameter name) => (array with PARAM_* constants as keys) |
329
|
|
|
* Don't call this function directly: use getFinalParams() to allow |
330
|
|
|
* hooks to modify parameters as needed. |
331
|
|
|
* |
332
|
|
|
* Some derived classes may choose to handle an integer $flags parameter |
333
|
|
|
* in the overriding methods. Callers of this method can pass zero or |
334
|
|
|
* more OR-ed flags like GET_VALUES_FOR_HELP. |
335
|
|
|
* |
336
|
|
|
* @return array |
337
|
|
|
*/ |
338
|
|
|
protected function getAllowedParams( /* $flags = 0 */ ) { |
339
|
|
|
// int $flags is not declared because it causes "Strict standards" |
340
|
|
|
// warning. Most derived classes do not implement it. |
341
|
|
|
return []; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* Indicates if this module needs maxlag to be checked |
346
|
|
|
* @return bool |
347
|
|
|
*/ |
348
|
|
|
public function shouldCheckMaxlag() { |
349
|
|
|
return true; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Indicates whether this module requires read rights |
354
|
|
|
* @return bool |
355
|
|
|
*/ |
356
|
|
|
public function isReadMode() { |
357
|
|
|
return true; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Indicates whether this module requires write mode |
362
|
|
|
* @return bool |
363
|
|
|
*/ |
364
|
|
|
public function isWriteMode() { |
365
|
|
|
return false; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* Indicates whether this module must be called with a POST request |
370
|
|
|
* @return bool |
371
|
|
|
*/ |
372
|
|
|
public function mustBePosted() { |
373
|
|
|
return $this->needsToken() !== false; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* Indicates whether this module is deprecated |
378
|
|
|
* @since 1.25 |
379
|
|
|
* @return bool |
380
|
|
|
*/ |
381
|
|
|
public function isDeprecated() { |
382
|
|
|
return false; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Indicates whether this module is "internal" |
387
|
|
|
* Internal API modules are not (yet) intended for 3rd party use and may be unstable. |
388
|
|
|
* @since 1.25 |
389
|
|
|
* @return bool |
390
|
|
|
*/ |
391
|
|
|
public function isInternal() { |
392
|
|
|
return false; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Returns the token type this module requires in order to execute. |
397
|
|
|
* |
398
|
|
|
* Modules are strongly encouraged to use the core 'csrf' type unless they |
399
|
|
|
* have specialized security needs. If the token type is not one of the |
400
|
|
|
* core types, you must use the ApiQueryTokensRegisterTypes hook to |
401
|
|
|
* register it. |
402
|
|
|
* |
403
|
|
|
* Returning a non-falsey value here will force the addition of an |
404
|
|
|
* appropriate 'token' parameter in self::getFinalParams(). Also, |
405
|
|
|
* self::mustBePosted() must return true when tokens are used. |
406
|
|
|
* |
407
|
|
|
* In previous versions of MediaWiki, true was a valid return value. |
408
|
|
|
* Returning true will generate errors indicating that the API module needs |
409
|
|
|
* updating. |
410
|
|
|
* |
411
|
|
|
* @return string|false |
412
|
|
|
*/ |
413
|
|
|
public function needsToken() { |
414
|
|
|
return false; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Fetch the salt used in the Web UI corresponding to this module. |
419
|
|
|
* |
420
|
|
|
* Only override this if the Web UI uses a token with a non-constant salt. |
421
|
|
|
* |
422
|
|
|
* @since 1.24 |
423
|
|
|
* @param array $params All supplied parameters for the module |
424
|
|
|
* @return string|array|null |
425
|
|
|
*/ |
426
|
|
|
protected function getWebUITokenSalt( array $params ) { |
427
|
|
|
return null; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* Returns data for HTTP conditional request mechanisms. |
432
|
|
|
* |
433
|
|
|
* @since 1.26 |
434
|
|
|
* @param string $condition Condition being queried: |
435
|
|
|
* - last-modified: Return a timestamp representing the maximum of the |
436
|
|
|
* last-modified dates for all resources involved in the request. See |
437
|
|
|
* RFC 7232 § 2.2 for semantics. |
438
|
|
|
* - etag: Return an entity-tag representing the state of all resources involved |
439
|
|
|
* in the request. Quotes must be included. See RFC 7232 § 2.3 for semantics. |
440
|
|
|
* @return string|bool|null As described above, or null if no value is available. |
441
|
|
|
*/ |
442
|
|
|
public function getConditionalRequestData( $condition ) { |
443
|
|
|
return null; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/**@}*/ |
447
|
|
|
|
448
|
|
|
/************************************************************************//** |
449
|
|
|
* @name Data access methods |
450
|
|
|
* @{ |
451
|
|
|
*/ |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* Get the name of the module being executed by this instance |
455
|
|
|
* @return string |
456
|
|
|
*/ |
457
|
|
|
public function getModuleName() { |
458
|
|
|
return $this->mModuleName; |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Get parameter prefix (usually two letters or an empty string). |
463
|
|
|
* @return string |
464
|
|
|
*/ |
465
|
|
|
public function getModulePrefix() { |
466
|
|
|
return $this->mModulePrefix; |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** |
470
|
|
|
* Get the main module |
471
|
|
|
* @return ApiMain |
472
|
|
|
*/ |
473
|
|
|
public function getMain() { |
474
|
|
|
return $this->mMainModule; |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
/** |
478
|
|
|
* Returns true if this module is the main module ($this === $this->mMainModule), |
479
|
|
|
* false otherwise. |
480
|
|
|
* @return bool |
481
|
|
|
*/ |
482
|
|
|
public function isMain() { |
483
|
|
|
return $this === $this->mMainModule; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Get the parent of this module |
488
|
|
|
* @since 1.25 |
489
|
|
|
* @return ApiBase|null |
490
|
|
|
*/ |
491
|
|
|
public function getParent() { |
492
|
|
|
return $this->isMain() ? null : $this->getMain(); |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* Returns true if the current request breaks the same-origin policy. |
497
|
|
|
* |
498
|
|
|
* For example, json with callbacks. |
499
|
|
|
* |
500
|
|
|
* https://en.wikipedia.org/wiki/Same-origin_policy |
501
|
|
|
* |
502
|
|
|
* @since 1.25 |
503
|
|
|
* @return bool |
504
|
|
|
*/ |
505
|
|
|
public function lacksSameOriginSecurity() { |
506
|
|
|
// Main module has this method overridden |
507
|
|
|
// Safety - avoid infinite loop: |
508
|
|
|
if ( $this->isMain() ) { |
509
|
|
|
ApiBase::dieDebug( __METHOD__, 'base method was called on main module.' ); |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
return $this->getMain()->lacksSameOriginSecurity(); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* Get the path to this module |
517
|
|
|
* |
518
|
|
|
* @since 1.25 |
519
|
|
|
* @return string |
520
|
|
|
*/ |
521
|
|
|
public function getModulePath() { |
522
|
|
|
if ( $this->isMain() ) { |
523
|
|
|
return 'main'; |
524
|
|
|
} elseif ( $this->getParent()->isMain() ) { |
525
|
|
|
return $this->getModuleName(); |
526
|
|
|
} else { |
527
|
|
|
return $this->getParent()->getModulePath() . '+' . $this->getModuleName(); |
528
|
|
|
} |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
/** |
532
|
|
|
* Get a module from its module path |
533
|
|
|
* |
534
|
|
|
* @since 1.25 |
535
|
|
|
* @param string $path |
536
|
|
|
* @return ApiBase|null |
537
|
|
|
* @throws UsageException |
538
|
|
|
*/ |
539
|
|
|
public function getModuleFromPath( $path ) { |
540
|
|
|
$module = $this->getMain(); |
541
|
|
|
if ( $path === 'main' ) { |
542
|
|
|
return $module; |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
$parts = explode( '+', $path ); |
546
|
|
|
if ( count( $parts ) === 1 ) { |
547
|
|
|
// In case the '+' was typed into URL, it resolves as a space |
548
|
|
|
$parts = explode( ' ', $path ); |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
$count = count( $parts ); |
552
|
|
|
for ( $i = 0; $i < $count; $i++ ) { |
553
|
|
|
$parent = $module; |
554
|
|
|
$manager = $parent->getModuleManager(); |
555
|
|
|
if ( $manager === null ) { |
556
|
|
|
$errorPath = implode( '+', array_slice( $parts, 0, $i ) ); |
557
|
|
|
$this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' ); |
558
|
|
|
} |
559
|
|
|
$module = $manager->getModule( $parts[$i] ); |
560
|
|
|
|
561
|
|
|
if ( $module === null ) { |
562
|
|
|
$errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName(); |
563
|
|
|
$this->dieUsage( |
564
|
|
|
"The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"", |
565
|
|
|
'badmodule' |
566
|
|
|
); |
567
|
|
|
} |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
return $module; |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
/** |
574
|
|
|
* Get the result object |
575
|
|
|
* @return ApiResult |
576
|
|
|
*/ |
577
|
|
|
public function getResult() { |
578
|
|
|
// Main module has getResult() method overridden |
579
|
|
|
// Safety - avoid infinite loop: |
580
|
|
|
if ( $this->isMain() ) { |
581
|
|
|
ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' ); |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
return $this->getMain()->getResult(); |
585
|
|
|
} |
586
|
|
|
|
587
|
|
|
/** |
588
|
|
|
* Get the error formatter |
589
|
|
|
* @return ApiErrorFormatter |
590
|
|
|
*/ |
591
|
|
|
public function getErrorFormatter() { |
592
|
|
|
// Main module has getErrorFormatter() method overridden |
593
|
|
|
// Safety - avoid infinite loop: |
594
|
|
|
if ( $this->isMain() ) { |
595
|
|
|
ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' ); |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
return $this->getMain()->getErrorFormatter(); |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* Gets a default replica DB connection object |
603
|
|
|
* @return Database |
604
|
|
|
*/ |
605
|
|
|
protected function getDB() { |
606
|
|
|
if ( !isset( $this->mSlaveDB ) ) { |
607
|
|
|
$this->mSlaveDB = wfGetDB( DB_REPLICA, 'api' ); |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
return $this->mSlaveDB; |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* Get the continuation manager |
615
|
|
|
* @return ApiContinuationManager|null |
616
|
|
|
*/ |
617
|
|
|
public function getContinuationManager() { |
618
|
|
|
// Main module has getContinuationManager() method overridden |
619
|
|
|
// Safety - avoid infinite loop: |
620
|
|
|
if ( $this->isMain() ) { |
621
|
|
|
ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' ); |
622
|
|
|
} |
623
|
|
|
|
624
|
|
|
return $this->getMain()->getContinuationManager(); |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
/** |
628
|
|
|
* Set the continuation manager |
629
|
|
|
* @param ApiContinuationManager|null |
630
|
|
|
*/ |
631
|
|
|
public function setContinuationManager( $manager ) { |
632
|
|
|
// Main module has setContinuationManager() method overridden |
633
|
|
|
// Safety - avoid infinite loop: |
634
|
|
|
if ( $this->isMain() ) { |
635
|
|
|
ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' ); |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
$this->getMain()->setContinuationManager( $manager ); |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
/**@}*/ |
642
|
|
|
|
643
|
|
|
/************************************************************************//** |
644
|
|
|
* @name Parameter handling |
645
|
|
|
* @{ |
646
|
|
|
*/ |
647
|
|
|
|
648
|
|
|
/** |
649
|
|
|
* Indicate if the module supports dynamically-determined parameters that |
650
|
|
|
* cannot be included in self::getAllowedParams(). |
651
|
|
|
* @return string|array|Message|null Return null if the module does not |
652
|
|
|
* support additional dynamic parameters, otherwise return a message |
653
|
|
|
* describing them. |
654
|
|
|
*/ |
655
|
|
|
public function dynamicParameterDocumentation() { |
656
|
|
|
return null; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
/** |
660
|
|
|
* This method mangles parameter name based on the prefix supplied to the constructor. |
661
|
|
|
* Override this method to change parameter name during runtime |
662
|
|
|
* @param string $paramName Parameter name |
663
|
|
|
* @return string Prefixed parameter name |
664
|
|
|
*/ |
665
|
|
|
public function encodeParamName( $paramName ) { |
666
|
|
|
return $this->mModulePrefix . $paramName; |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
/** |
670
|
|
|
* Using getAllowedParams(), this function makes an array of the values |
671
|
|
|
* provided by the user, with key being the name of the variable, and |
672
|
|
|
* value - validated value from user or default. limits will not be |
673
|
|
|
* parsed if $parseLimit is set to false; use this when the max |
674
|
|
|
* limit is not definitive yet, e.g. when getting revisions. |
675
|
|
|
* @param bool $parseLimit True by default |
676
|
|
|
* @return array |
677
|
|
|
*/ |
678
|
|
|
public function extractRequestParams( $parseLimit = true ) { |
679
|
|
|
// Cache parameters, for performance and to avoid bug 24564. |
680
|
|
|
if ( !isset( $this->mParamCache[$parseLimit] ) ) { |
681
|
|
|
$params = $this->getFinalParams(); |
682
|
|
|
$results = []; |
683
|
|
|
|
684
|
|
|
if ( $params ) { // getFinalParams() can return false |
685
|
|
|
foreach ( $params as $paramName => $paramSettings ) { |
|
|
|
|
686
|
|
|
$results[$paramName] = $this->getParameterFromSettings( |
687
|
|
|
$paramName, $paramSettings, $parseLimit ); |
688
|
|
|
} |
689
|
|
|
} |
690
|
|
|
$this->mParamCache[$parseLimit] = $results; |
691
|
|
|
} |
692
|
|
|
|
693
|
|
|
return $this->mParamCache[$parseLimit]; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* Get a value for the given parameter |
698
|
|
|
* @param string $paramName Parameter name |
699
|
|
|
* @param bool $parseLimit See extractRequestParams() |
700
|
|
|
* @return mixed Parameter value |
701
|
|
|
*/ |
702
|
|
|
protected function getParameter( $paramName, $parseLimit = true ) { |
703
|
|
|
$paramSettings = $this->getFinalParams()[$paramName]; |
704
|
|
|
|
705
|
|
|
return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit ); |
706
|
|
|
} |
707
|
|
|
|
708
|
|
|
/** |
709
|
|
|
* Die if none or more than one of a certain set of parameters is set and not false. |
710
|
|
|
* |
711
|
|
|
* @param array $params User provided set of parameters, as from $this->extractRequestParams() |
712
|
|
|
* @param string $required,... Names of parameters of which exactly one must be set |
|
|
|
|
713
|
|
|
*/ |
714
|
|
|
public function requireOnlyOneParameter( $params, $required /*...*/ ) { |
715
|
|
|
$required = func_get_args(); |
716
|
|
|
array_shift( $required ); |
717
|
|
|
$p = $this->getModulePrefix(); |
718
|
|
|
|
719
|
|
|
$intersection = array_intersect( array_keys( array_filter( $params, |
720
|
|
|
[ $this, 'parameterNotEmpty' ] ) ), $required ); |
721
|
|
|
|
722
|
|
|
if ( count( $intersection ) > 1 ) { |
723
|
|
|
$this->dieUsage( |
724
|
|
|
"The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', |
725
|
|
|
'invalidparammix' ); |
726
|
|
|
} elseif ( count( $intersection ) == 0 ) { |
727
|
|
|
$this->dieUsage( |
728
|
|
|
"One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', |
729
|
|
|
'missingparam' |
730
|
|
|
); |
731
|
|
|
} |
732
|
|
|
} |
733
|
|
|
|
734
|
|
|
/** |
735
|
|
|
* Die if more than one of a certain set of parameters is set and not false. |
736
|
|
|
* |
737
|
|
|
* @param array $params User provided set of parameters, as from $this->extractRequestParams() |
738
|
|
|
* @param string $required,... Names of parameters of which at most one must be set |
|
|
|
|
739
|
|
|
*/ |
740
|
|
View Code Duplication |
public function requireMaxOneParameter( $params, $required /*...*/ ) { |
741
|
|
|
$required = func_get_args(); |
742
|
|
|
array_shift( $required ); |
743
|
|
|
$p = $this->getModulePrefix(); |
744
|
|
|
|
745
|
|
|
$intersection = array_intersect( array_keys( array_filter( $params, |
746
|
|
|
[ $this, 'parameterNotEmpty' ] ) ), $required ); |
747
|
|
|
|
748
|
|
|
if ( count( $intersection ) > 1 ) { |
749
|
|
|
$this->dieUsage( |
750
|
|
|
"The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', |
751
|
|
|
'invalidparammix' |
752
|
|
|
); |
753
|
|
|
} |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* Die if none of a certain set of parameters is set and not false. |
758
|
|
|
* |
759
|
|
|
* @since 1.23 |
760
|
|
|
* @param array $params User provided set of parameters, as from $this->extractRequestParams() |
761
|
|
|
* @param string $required,... Names of parameters of which at least one must be set |
|
|
|
|
762
|
|
|
*/ |
763
|
|
View Code Duplication |
public function requireAtLeastOneParameter( $params, $required /*...*/ ) { |
764
|
|
|
$required = func_get_args(); |
765
|
|
|
array_shift( $required ); |
766
|
|
|
$p = $this->getModulePrefix(); |
767
|
|
|
|
768
|
|
|
$intersection = array_intersect( |
769
|
|
|
array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ), |
770
|
|
|
$required |
771
|
|
|
); |
772
|
|
|
|
773
|
|
|
if ( count( $intersection ) == 0 ) { |
774
|
|
|
$this->dieUsage( "At least one of the parameters {$p}" . |
775
|
|
|
implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" ); |
776
|
|
|
} |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
/** |
780
|
|
|
* Die if any of the specified parameters were found in the query part of |
781
|
|
|
* the URL rather than the post body. |
782
|
|
|
* @since 1.28 |
783
|
|
|
* @param string[] $params Parameters to check |
784
|
|
|
* @param string $prefix Set to 'noprefix' to skip calling $this->encodeParamName() |
785
|
|
|
*/ |
786
|
|
|
public function requirePostedParameters( $params, $prefix = 'prefix' ) { |
787
|
|
|
// Skip if $wgDebugAPI is set or we're in internal mode |
788
|
|
|
if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) { |
789
|
|
|
return; |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
$queryValues = $this->getRequest()->getQueryValues(); |
793
|
|
|
$badParams = []; |
794
|
|
|
foreach ( $params as $param ) { |
795
|
|
|
if ( $prefix !== 'noprefix' ) { |
796
|
|
|
$param = $this->encodeParamName( $param ); |
797
|
|
|
} |
798
|
|
|
if ( array_key_exists( $param, $queryValues ) ) { |
799
|
|
|
$badParams[] = $param; |
800
|
|
|
} |
801
|
|
|
} |
802
|
|
|
|
803
|
|
|
if ( $badParams ) { |
804
|
|
|
$this->dieUsage( |
805
|
|
|
'The following parameters were found in the query string, but must be in the POST body: ' |
806
|
|
|
. join( ', ', $badParams ), |
807
|
|
|
'mustpostparams' |
808
|
|
|
); |
809
|
|
|
} |
810
|
|
|
} |
811
|
|
|
|
812
|
|
|
/** |
813
|
|
|
* Callback function used in requireOnlyOneParameter to check whether required parameters are set |
814
|
|
|
* |
815
|
|
|
* @param object $x Parameter to check is not null/false |
816
|
|
|
* @return bool |
817
|
|
|
*/ |
818
|
|
|
private function parameterNotEmpty( $x ) { |
819
|
|
|
return !is_null( $x ) && $x !== false; |
820
|
|
|
} |
821
|
|
|
|
822
|
|
|
/** |
823
|
|
|
* Get a WikiPage object from a title or pageid param, if possible. |
824
|
|
|
* Can die, if no param is set or if the title or page id is not valid. |
825
|
|
|
* |
826
|
|
|
* @param array $params |
827
|
|
|
* @param bool|string $load Whether load the object's state from the database: |
828
|
|
|
* - false: don't load (if the pageid is given, it will still be loaded) |
829
|
|
|
* - 'fromdb': load from a replica DB |
830
|
|
|
* - 'fromdbmaster': load from the master database |
831
|
|
|
* @return WikiPage |
832
|
|
|
*/ |
833
|
|
|
public function getTitleOrPageId( $params, $load = false ) { |
834
|
|
|
$this->requireOnlyOneParameter( $params, 'title', 'pageid' ); |
835
|
|
|
|
836
|
|
|
$pageObj = null; |
837
|
|
|
if ( isset( $params['title'] ) ) { |
838
|
|
|
$titleObj = Title::newFromText( $params['title'] ); |
839
|
|
|
if ( !$titleObj || $titleObj->isExternal() ) { |
840
|
|
|
$this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] ); |
841
|
|
|
} |
842
|
|
|
if ( !$titleObj->canExist() ) { |
843
|
|
|
$this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' ); |
844
|
|
|
} |
845
|
|
|
$pageObj = WikiPage::factory( $titleObj ); |
|
|
|
|
846
|
|
|
if ( $load !== false ) { |
847
|
|
|
$pageObj->loadPageData( $load ); |
|
|
|
|
848
|
|
|
} |
849
|
|
|
} elseif ( isset( $params['pageid'] ) ) { |
850
|
|
|
if ( $load === false ) { |
851
|
|
|
$load = 'fromdb'; |
852
|
|
|
} |
853
|
|
|
$pageObj = WikiPage::newFromID( $params['pageid'], $load ); |
|
|
|
|
854
|
|
|
if ( !$pageObj ) { |
855
|
|
|
$this->dieUsageMsg( [ 'nosuchpageid', $params['pageid'] ] ); |
856
|
|
|
} |
857
|
|
|
} |
858
|
|
|
|
859
|
|
|
return $pageObj; |
860
|
|
|
} |
861
|
|
|
|
862
|
|
|
/** |
863
|
|
|
* Return true if we're to watch the page, false if not, null if no change. |
864
|
|
|
* @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange' |
865
|
|
|
* @param Title $titleObj The page under consideration |
866
|
|
|
* @param string $userOption The user option to consider when $watchlist=preferences. |
867
|
|
|
* If not set will use watchdefault always and watchcreations if $titleObj doesn't exist. |
868
|
|
|
* @return bool |
869
|
|
|
*/ |
870
|
|
|
protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) { |
871
|
|
|
|
872
|
|
|
$userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS ); |
873
|
|
|
|
874
|
|
|
switch ( $watchlist ) { |
875
|
|
|
case 'watch': |
876
|
|
|
return true; |
877
|
|
|
|
878
|
|
|
case 'unwatch': |
879
|
|
|
return false; |
880
|
|
|
|
881
|
|
|
case 'preferences': |
882
|
|
|
# If the user is already watching, don't bother checking |
883
|
|
|
if ( $userWatching ) { |
884
|
|
|
return true; |
885
|
|
|
} |
886
|
|
|
# If no user option was passed, use watchdefault and watchcreations |
887
|
|
|
if ( is_null( $userOption ) ) { |
888
|
|
|
return $this->getUser()->getBoolOption( 'watchdefault' ) || |
889
|
|
|
$this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists(); |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
# Watch the article based on the user preference |
893
|
|
|
return $this->getUser()->getBoolOption( $userOption ); |
894
|
|
|
|
895
|
|
|
case 'nochange': |
896
|
|
|
return $userWatching; |
897
|
|
|
|
898
|
|
|
default: |
899
|
|
|
return $userWatching; |
900
|
|
|
} |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
/** |
904
|
|
|
* Using the settings determine the value for the given parameter |
905
|
|
|
* |
906
|
|
|
* @param string $paramName Parameter name |
907
|
|
|
* @param array|mixed $paramSettings Default value or an array of settings |
908
|
|
|
* using PARAM_* constants. |
909
|
|
|
* @param bool $parseLimit Parse limit? |
910
|
|
|
* @return mixed Parameter value |
911
|
|
|
*/ |
912
|
|
|
protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) { |
913
|
|
|
// Some classes may decide to change parameter names |
914
|
|
|
$encParamName = $this->encodeParamName( $paramName ); |
915
|
|
|
|
916
|
|
|
if ( !is_array( $paramSettings ) ) { |
917
|
|
|
$default = $paramSettings; |
918
|
|
|
$multi = false; |
919
|
|
|
$type = gettype( $paramSettings ); |
920
|
|
|
$dupes = false; |
921
|
|
|
$deprecated = false; |
922
|
|
|
$required = false; |
923
|
|
|
} else { |
924
|
|
|
$default = isset( $paramSettings[self::PARAM_DFLT] ) |
925
|
|
|
? $paramSettings[self::PARAM_DFLT] |
926
|
|
|
: null; |
927
|
|
|
$multi = isset( $paramSettings[self::PARAM_ISMULTI] ) |
928
|
|
|
? $paramSettings[self::PARAM_ISMULTI] |
929
|
|
|
: false; |
930
|
|
|
$type = isset( $paramSettings[self::PARAM_TYPE] ) |
931
|
|
|
? $paramSettings[self::PARAM_TYPE] |
932
|
|
|
: null; |
933
|
|
|
$dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] ) |
934
|
|
|
? $paramSettings[self::PARAM_ALLOW_DUPLICATES] |
935
|
|
|
: false; |
936
|
|
|
$deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) |
937
|
|
|
? $paramSettings[self::PARAM_DEPRECATED] |
938
|
|
|
: false; |
939
|
|
|
$required = isset( $paramSettings[self::PARAM_REQUIRED] ) |
940
|
|
|
? $paramSettings[self::PARAM_REQUIRED] |
941
|
|
|
: false; |
942
|
|
|
|
943
|
|
|
// When type is not given, and no choices, the type is the same as $default |
944
|
|
|
if ( !isset( $type ) ) { |
945
|
|
|
if ( isset( $default ) ) { |
946
|
|
|
$type = gettype( $default ); |
947
|
|
|
} else { |
948
|
|
|
$type = 'NULL'; // allow everything |
949
|
|
|
} |
950
|
|
|
} |
951
|
|
|
} |
952
|
|
|
|
953
|
|
|
if ( $type == 'boolean' ) { |
954
|
|
|
if ( isset( $default ) && $default !== false ) { |
955
|
|
|
// Having a default value of anything other than 'false' is not allowed |
956
|
|
|
ApiBase::dieDebug( |
957
|
|
|
__METHOD__, |
958
|
|
|
"Boolean param $encParamName's default is set to '$default'. " . |
959
|
|
|
'Boolean parameters must default to false.' |
960
|
|
|
); |
961
|
|
|
} |
962
|
|
|
|
963
|
|
|
$value = $this->getMain()->getCheck( $encParamName ); |
964
|
|
|
} elseif ( $type == 'upload' ) { |
965
|
|
|
if ( isset( $default ) ) { |
966
|
|
|
// Having a default value is not allowed |
967
|
|
|
ApiBase::dieDebug( |
968
|
|
|
__METHOD__, |
969
|
|
|
"File upload param $encParamName's default is set to " . |
970
|
|
|
"'$default'. File upload parameters may not have a default." ); |
971
|
|
|
} |
972
|
|
|
if ( $multi ) { |
973
|
|
|
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" ); |
974
|
|
|
} |
975
|
|
|
$value = $this->getMain()->getUpload( $encParamName ); |
976
|
|
|
if ( !$value->exists() ) { |
977
|
|
|
// This will get the value without trying to normalize it |
978
|
|
|
// (because trying to normalize a large binary file |
979
|
|
|
// accidentally uploaded as a field fails spectacularly) |
980
|
|
|
$value = $this->getMain()->getRequest()->unsetVal( $encParamName ); |
981
|
|
|
if ( $value !== null ) { |
982
|
|
|
$this->dieUsage( |
983
|
|
|
"File upload param $encParamName is not a file upload; " . |
984
|
|
|
'be sure to use multipart/form-data for your POST and include ' . |
985
|
|
|
'a filename in the Content-Disposition header.', |
986
|
|
|
"badupload_{$encParamName}" |
987
|
|
|
); |
988
|
|
|
} |
989
|
|
|
} |
990
|
|
|
} else { |
991
|
|
|
$value = $this->getMain()->getVal( $encParamName, $default ); |
992
|
|
|
|
993
|
|
|
if ( isset( $value ) && $type == 'namespace' ) { |
994
|
|
|
$type = MWNamespace::getValidNamespaces(); |
995
|
|
|
} |
996
|
|
|
if ( isset( $value ) && $type == 'submodule' ) { |
997
|
|
|
if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) { |
998
|
|
|
$type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] ); |
999
|
|
|
} else { |
1000
|
|
|
$type = $this->getModuleManager()->getNames( $paramName ); |
1001
|
|
|
} |
1002
|
|
|
} |
1003
|
|
|
|
1004
|
|
|
$request = $this->getMain()->getRequest(); |
1005
|
|
|
$rawValue = $request->getRawVal( $encParamName ); |
1006
|
|
|
if ( $rawValue === null ) { |
1007
|
|
|
$rawValue = $default; |
1008
|
|
|
} |
1009
|
|
|
|
1010
|
|
|
// Preserve U+001F for self::parseMultiValue(), or error out if that won't be called |
1011
|
|
|
if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) { |
1012
|
|
|
if ( $multi ) { |
1013
|
|
|
// This loses the potential $wgContLang->checkTitleEncoding() transformation |
1014
|
|
|
// done by WebRequest for $_GET. Let's call that a feature. |
1015
|
|
|
$value = join( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) ); |
1016
|
|
|
} else { |
1017
|
|
|
$this->dieUsage( |
1018
|
|
|
"U+001F multi-value separation may only be used for multi-valued parameters.", |
1019
|
|
|
'badvalue_notmultivalue' |
1020
|
|
|
); |
1021
|
|
|
} |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
// Check for NFC normalization, and warn |
1025
|
|
|
if ( $rawValue !== $value ) { |
1026
|
|
|
$this->handleParamNormalization( $paramName, $value, $rawValue ); |
1027
|
|
|
} |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
if ( isset( $value ) && ( $multi || is_array( $type ) ) ) { |
1031
|
|
|
$value = $this->parseMultiValue( |
1032
|
|
|
$encParamName, |
1033
|
|
|
$value, |
1034
|
|
|
$multi, |
1035
|
|
|
is_array( $type ) ? $type : null |
|
|
|
|
1036
|
|
|
); |
1037
|
|
|
} |
1038
|
|
|
|
1039
|
|
|
// More validation only when choices were not given |
1040
|
|
|
// choices were validated in parseMultiValue() |
1041
|
|
|
if ( isset( $value ) ) { |
1042
|
|
|
if ( !is_array( $type ) ) { |
1043
|
|
|
switch ( $type ) { |
1044
|
|
|
case 'NULL': // nothing to do |
1045
|
|
|
break; |
1046
|
|
|
case 'string': |
1047
|
|
|
case 'text': |
1048
|
|
|
case 'password': |
1049
|
|
|
if ( $required && $value === '' ) { |
1050
|
|
|
$this->dieUsageMsg( [ 'missingparam', $paramName ] ); |
1051
|
|
|
} |
1052
|
|
|
break; |
1053
|
|
|
case 'integer': // Force everything using intval() and optionally validate limits |
1054
|
|
|
$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null; |
1055
|
|
|
$max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null; |
1056
|
|
|
$enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] ) |
1057
|
|
|
? $paramSettings[self::PARAM_RANGE_ENFORCE] : false; |
1058
|
|
|
|
1059
|
|
|
if ( is_array( $value ) ) { |
1060
|
|
|
$value = array_map( 'intval', $value ); |
1061
|
|
View Code Duplication |
if ( !is_null( $min ) || !is_null( $max ) ) { |
1062
|
|
|
foreach ( $value as &$v ) { |
1063
|
|
|
$this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits ); |
1064
|
|
|
} |
1065
|
|
|
} |
1066
|
|
View Code Duplication |
} else { |
1067
|
|
|
$value = intval( $value ); |
1068
|
|
|
if ( !is_null( $min ) || !is_null( $max ) ) { |
1069
|
|
|
$this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits ); |
1070
|
|
|
} |
1071
|
|
|
} |
1072
|
|
|
break; |
1073
|
|
|
case 'limit': |
1074
|
|
|
if ( !$parseLimit ) { |
1075
|
|
|
// Don't do any validation whatsoever |
1076
|
|
|
break; |
1077
|
|
|
} |
1078
|
|
|
if ( !isset( $paramSettings[self::PARAM_MAX] ) |
1079
|
|
|
|| !isset( $paramSettings[self::PARAM_MAX2] ) |
1080
|
|
|
) { |
1081
|
|
|
ApiBase::dieDebug( |
1082
|
|
|
__METHOD__, |
1083
|
|
|
"MAX1 or MAX2 are not defined for the limit $encParamName" |
1084
|
|
|
); |
1085
|
|
|
} |
1086
|
|
|
if ( $multi ) { |
1087
|
|
|
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" ); |
1088
|
|
|
} |
1089
|
|
|
$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0; |
1090
|
|
|
if ( $value == 'max' ) { |
1091
|
|
|
$value = $this->getMain()->canApiHighLimits() |
1092
|
|
|
? $paramSettings[self::PARAM_MAX2] |
1093
|
|
|
: $paramSettings[self::PARAM_MAX]; |
1094
|
|
|
$this->getResult()->addParsedLimit( $this->getModuleName(), $value ); |
1095
|
|
|
} else { |
1096
|
|
|
$value = intval( $value ); |
1097
|
|
|
$this->validateLimit( |
1098
|
|
|
$paramName, |
1099
|
|
|
$value, |
1100
|
|
|
$min, |
1101
|
|
|
$paramSettings[self::PARAM_MAX], |
1102
|
|
|
$paramSettings[self::PARAM_MAX2] |
1103
|
|
|
); |
1104
|
|
|
} |
1105
|
|
|
break; |
1106
|
|
|
case 'boolean': |
1107
|
|
|
if ( $multi ) { |
1108
|
|
|
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" ); |
1109
|
|
|
} |
1110
|
|
|
break; |
1111
|
|
View Code Duplication |
case 'timestamp': |
1112
|
|
|
if ( is_array( $value ) ) { |
1113
|
|
|
foreach ( $value as $key => $val ) { |
1114
|
|
|
$value[$key] = $this->validateTimestamp( $val, $encParamName ); |
1115
|
|
|
} |
1116
|
|
|
} else { |
1117
|
|
|
$value = $this->validateTimestamp( $value, $encParamName ); |
1118
|
|
|
} |
1119
|
|
|
break; |
1120
|
|
View Code Duplication |
case 'user': |
1121
|
|
|
if ( is_array( $value ) ) { |
1122
|
|
|
foreach ( $value as $key => $val ) { |
1123
|
|
|
$value[$key] = $this->validateUser( $val, $encParamName ); |
1124
|
|
|
} |
1125
|
|
|
} else { |
1126
|
|
|
$value = $this->validateUser( $value, $encParamName ); |
1127
|
|
|
} |
1128
|
|
|
break; |
1129
|
|
|
case 'upload': // nothing to do |
1130
|
|
|
break; |
1131
|
|
|
case 'tags': |
1132
|
|
|
// If change tagging was requested, check that the tags are valid. |
1133
|
|
|
if ( !is_array( $value ) && !$multi ) { |
1134
|
|
|
$value = [ $value ]; |
1135
|
|
|
} |
1136
|
|
|
$tagsStatus = ChangeTags::canAddTagsAccompanyingChange( $value ); |
1137
|
|
|
if ( !$tagsStatus->isGood() ) { |
1138
|
|
|
$this->dieStatus( $tagsStatus ); |
1139
|
|
|
} |
1140
|
|
|
break; |
1141
|
|
|
default: |
1142
|
|
|
ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" ); |
1143
|
|
|
} |
1144
|
|
|
} |
1145
|
|
|
|
1146
|
|
|
// Throw out duplicates if requested |
1147
|
|
|
if ( !$dupes && is_array( $value ) ) { |
1148
|
|
|
$value = array_unique( $value ); |
1149
|
|
|
} |
1150
|
|
|
|
1151
|
|
|
// Set a warning if a deprecated parameter has been passed |
1152
|
|
|
if ( $deprecated && $value !== false ) { |
1153
|
|
|
$this->setWarning( "The $encParamName parameter has been deprecated." ); |
1154
|
|
|
|
1155
|
|
|
$feature = $encParamName; |
1156
|
|
|
$m = $this; |
1157
|
|
|
while ( !$m->isMain() ) { |
1158
|
|
|
$p = $m->getParent(); |
1159
|
|
|
$name = $m->getModuleName(); |
1160
|
|
|
$param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) ); |
1161
|
|
|
$feature = "{$param}={$name}&{$feature}"; |
1162
|
|
|
$m = $p; |
1163
|
|
|
} |
1164
|
|
|
$this->logFeatureUsage( $feature ); |
1165
|
|
|
} |
1166
|
|
|
} elseif ( $required ) { |
1167
|
|
|
$this->dieUsageMsg( [ 'missingparam', $paramName ] ); |
1168
|
|
|
} |
1169
|
|
|
|
1170
|
|
|
return $value; |
1171
|
|
|
} |
1172
|
|
|
|
1173
|
|
|
/** |
1174
|
|
|
* Handle when a parameter was Unicode-normalized |
1175
|
|
|
* @since 1.28 |
1176
|
|
|
* @param string $paramName Unprefixed parameter name |
1177
|
|
|
* @param string $value Input that will be used. |
1178
|
|
|
* @param string $rawValue Input before normalization. |
1179
|
|
|
*/ |
1180
|
|
|
protected function handleParamNormalization( $paramName, $value, $rawValue ) { |
1181
|
|
|
$encParamName = $this->encodeParamName( $paramName ); |
1182
|
|
|
$this->setWarning( |
1183
|
|
|
"The value passed for '$encParamName' contains invalid or non-normalized data. " |
1184
|
|
|
. 'Textual data should be valid, NFC-normalized Unicode without ' |
1185
|
|
|
. 'C0 control characters other than HT (\\t), LF (\\n), and CR (\\r).' |
1186
|
|
|
); |
1187
|
|
|
} |
1188
|
|
|
|
1189
|
|
|
/** |
1190
|
|
|
* Split a multi-valued parameter string, like explode() |
1191
|
|
|
* @since 1.28 |
1192
|
|
|
* @param string $value |
1193
|
|
|
* @param int $limit |
1194
|
|
|
* @return string[] |
1195
|
|
|
*/ |
1196
|
|
|
protected function explodeMultiValue( $value, $limit ) { |
1197
|
|
|
if ( substr( $value, 0, 1 ) === "\x1f" ) { |
1198
|
|
|
$sep = "\x1f"; |
1199
|
|
|
$value = substr( $value, 1 ); |
1200
|
|
|
} else { |
1201
|
|
|
$sep = '|'; |
1202
|
|
|
} |
1203
|
|
|
|
1204
|
|
|
return explode( $sep, $value, $limit ); |
1205
|
|
|
} |
1206
|
|
|
|
1207
|
|
|
/** |
1208
|
|
|
* Return an array of values that were given in a 'a|b|c' notation, |
1209
|
|
|
* after it optionally validates them against the list allowed values. |
1210
|
|
|
* |
1211
|
|
|
* @param string $valueName The name of the parameter (for error |
1212
|
|
|
* reporting) |
1213
|
|
|
* @param mixed $value The value being parsed |
1214
|
|
|
* @param bool $allowMultiple Can $value contain more than one value |
1215
|
|
|
* separated by '|'? |
1216
|
|
|
* @param string[]|null $allowedValues An array of values to check against. If |
1217
|
|
|
* null, all values are accepted. |
1218
|
|
|
* @return string|string[] (allowMultiple ? an_array_of_values : a_single_value) |
1219
|
|
|
*/ |
1220
|
|
|
protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) { |
1221
|
|
|
if ( ( trim( $value ) === '' || trim( $value ) === "\x1f" ) && $allowMultiple ) { |
1222
|
|
|
return []; |
1223
|
|
|
} |
1224
|
|
|
|
1225
|
|
|
// This is a bit awkward, but we want to avoid calling canApiHighLimits() |
1226
|
|
|
// because it unstubs $wgUser |
1227
|
|
|
$valuesList = $this->explodeMultiValue( $value, self::LIMIT_SML2 + 1 ); |
1228
|
|
|
$sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits() |
1229
|
|
|
? self::LIMIT_SML2 |
1230
|
|
|
: self::LIMIT_SML1; |
1231
|
|
|
|
1232
|
|
|
if ( self::truncateArray( $valuesList, $sizeLimit ) ) { |
1233
|
|
|
$this->logFeatureUsage( "too-many-$valueName-for-{$this->getModulePath()}" ); |
1234
|
|
|
$this->setWarning( "Too many values supplied for parameter '$valueName': " . |
1235
|
|
|
"the limit is $sizeLimit" ); |
1236
|
|
|
} |
1237
|
|
|
|
1238
|
|
|
if ( !$allowMultiple && count( $valuesList ) != 1 ) { |
1239
|
|
|
// Bug 33482 - Allow entries with | in them for non-multiple values |
1240
|
|
|
if ( in_array( $value, $allowedValues, true ) ) { |
1241
|
|
|
return $value; |
1242
|
|
|
} |
1243
|
|
|
|
1244
|
|
|
$possibleValues = is_array( $allowedValues ) |
1245
|
|
|
? "of '" . implode( "', '", $allowedValues ) . "'" |
1246
|
|
|
: ''; |
1247
|
|
|
$this->dieUsage( |
1248
|
|
|
"Only one $possibleValues is allowed for parameter '$valueName'", |
1249
|
|
|
"multival_$valueName" |
1250
|
|
|
); |
1251
|
|
|
} |
1252
|
|
|
|
1253
|
|
|
if ( is_array( $allowedValues ) ) { |
1254
|
|
|
// Check for unknown values |
1255
|
|
|
$unknown = array_diff( $valuesList, $allowedValues ); |
1256
|
|
|
if ( count( $unknown ) ) { |
1257
|
|
|
if ( $allowMultiple ) { |
1258
|
|
|
$s = count( $unknown ) > 1 ? 's' : ''; |
1259
|
|
|
$vals = implode( ', ', $unknown ); |
1260
|
|
|
$this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" ); |
1261
|
|
|
} else { |
1262
|
|
|
$this->dieUsage( |
1263
|
|
|
"Unrecognized value for parameter '$valueName': {$valuesList[0]}", |
1264
|
|
|
"unknown_$valueName" |
1265
|
|
|
); |
1266
|
|
|
} |
1267
|
|
|
} |
1268
|
|
|
// Now throw them out |
1269
|
|
|
$valuesList = array_intersect( $valuesList, $allowedValues ); |
1270
|
|
|
} |
1271
|
|
|
|
1272
|
|
|
return $allowMultiple ? $valuesList : $valuesList[0]; |
1273
|
|
|
} |
1274
|
|
|
|
1275
|
|
|
/** |
1276
|
|
|
* Validate the value against the minimum and user/bot maximum limits. |
1277
|
|
|
* Prints usage info on failure. |
1278
|
|
|
* @param string $paramName Parameter name |
1279
|
|
|
* @param int $value Parameter value |
1280
|
|
|
* @param int|null $min Minimum value |
1281
|
|
|
* @param int|null $max Maximum value for users |
1282
|
|
|
* @param int $botMax Maximum value for sysops/bots |
1283
|
|
|
* @param bool $enforceLimits Whether to enforce (die) if value is outside limits |
1284
|
|
|
*/ |
1285
|
|
|
protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null, |
1286
|
|
|
$enforceLimits = false |
1287
|
|
|
) { |
1288
|
|
|
if ( !is_null( $min ) && $value < $min ) { |
1289
|
|
|
$msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)"; |
1290
|
|
|
$this->warnOrDie( $msg, $enforceLimits ); |
1291
|
|
|
$value = $min; |
1292
|
|
|
} |
1293
|
|
|
|
1294
|
|
|
// Minimum is always validated, whereas maximum is checked only if not |
1295
|
|
|
// running in internal call mode |
1296
|
|
|
if ( $this->getMain()->isInternalMode() ) { |
1297
|
|
|
return; |
1298
|
|
|
} |
1299
|
|
|
|
1300
|
|
|
// Optimization: do not check user's bot status unless really needed -- skips db query |
1301
|
|
|
// assumes $botMax >= $max |
1302
|
|
|
if ( !is_null( $max ) && $value > $max ) { |
1303
|
|
|
if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) { |
1304
|
|
View Code Duplication |
if ( $value > $botMax ) { |
1305
|
|
|
$msg = $this->encodeParamName( $paramName ) . |
1306
|
|
|
" may not be over $botMax (set to $value) for bots or sysops"; |
1307
|
|
|
$this->warnOrDie( $msg, $enforceLimits ); |
1308
|
|
|
$value = $botMax; |
1309
|
|
|
} |
1310
|
|
View Code Duplication |
} else { |
1311
|
|
|
$msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users"; |
1312
|
|
|
$this->warnOrDie( $msg, $enforceLimits ); |
1313
|
|
|
$value = $max; |
1314
|
|
|
} |
1315
|
|
|
} |
1316
|
|
|
} |
1317
|
|
|
|
1318
|
|
|
/** |
1319
|
|
|
* Validate and normalize of parameters of type 'timestamp' |
1320
|
|
|
* @param string $value Parameter value |
1321
|
|
|
* @param string $encParamName Parameter name |
1322
|
|
|
* @return string Validated and normalized parameter |
1323
|
|
|
*/ |
1324
|
|
|
protected function validateTimestamp( $value, $encParamName ) { |
1325
|
|
|
// Confusing synonyms for the current time accepted by wfTimestamp() |
1326
|
|
|
// (wfTimestamp() also accepts various non-strings and the string of 14 |
1327
|
|
|
// ASCII NUL bytes, but those can't get here) |
1328
|
|
|
if ( !$value ) { |
1329
|
|
|
$this->logFeatureUsage( 'unclear-"now"-timestamp' ); |
1330
|
|
|
$this->setWarning( |
1331
|
|
|
"Passing '$value' for timestamp parameter $encParamName has been deprecated." . |
1332
|
|
|
' If for some reason you need to explicitly specify the current time without' . |
1333
|
|
|
' calculating it client-side, use "now".' |
1334
|
|
|
); |
1335
|
|
|
return wfTimestamp( TS_MW ); |
1336
|
|
|
} |
1337
|
|
|
|
1338
|
|
|
// Explicit synonym for the current time |
1339
|
|
|
if ( $value === 'now' ) { |
1340
|
|
|
return wfTimestamp( TS_MW ); |
1341
|
|
|
} |
1342
|
|
|
|
1343
|
|
|
$unixTimestamp = wfTimestamp( TS_UNIX, $value ); |
1344
|
|
|
if ( $unixTimestamp === false ) { |
1345
|
|
|
$this->dieUsage( |
1346
|
|
|
"Invalid value '$value' for timestamp parameter $encParamName", |
1347
|
|
|
"badtimestamp_{$encParamName}" |
1348
|
|
|
); |
1349
|
|
|
} |
1350
|
|
|
|
1351
|
|
|
return wfTimestamp( TS_MW, $unixTimestamp ); |
1352
|
|
|
} |
1353
|
|
|
|
1354
|
|
|
/** |
1355
|
|
|
* Validate the supplied token. |
1356
|
|
|
* |
1357
|
|
|
* @since 1.24 |
1358
|
|
|
* @param string $token Supplied token |
1359
|
|
|
* @param array $params All supplied parameters for the module |
1360
|
|
|
* @return bool |
1361
|
|
|
* @throws MWException |
1362
|
|
|
*/ |
1363
|
|
|
final public function validateToken( $token, array $params ) { |
1364
|
|
|
$tokenType = $this->needsToken(); |
1365
|
|
|
$salts = ApiQueryTokens::getTokenTypeSalts(); |
1366
|
|
|
if ( !isset( $salts[$tokenType] ) ) { |
1367
|
|
|
throw new MWException( |
1368
|
|
|
"Module '{$this->getModuleName()}' tried to use token type '$tokenType' " . |
1369
|
|
|
'without registering it' |
1370
|
|
|
); |
1371
|
|
|
} |
1372
|
|
|
|
1373
|
|
|
$tokenObj = ApiQueryTokens::getToken( |
1374
|
|
|
$this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType] |
1375
|
|
|
); |
1376
|
|
|
if ( $tokenObj->match( $token ) ) { |
1377
|
|
|
return true; |
1378
|
|
|
} |
1379
|
|
|
|
1380
|
|
|
$webUiSalt = $this->getWebUITokenSalt( $params ); |
1381
|
|
|
if ( $webUiSalt !== null && $this->getUser()->matchEditToken( |
1382
|
|
|
$token, |
1383
|
|
|
$webUiSalt, |
|
|
|
|
1384
|
|
|
$this->getRequest() |
1385
|
|
|
) ) { |
1386
|
|
|
return true; |
1387
|
|
|
} |
1388
|
|
|
|
1389
|
|
|
return false; |
1390
|
|
|
} |
1391
|
|
|
|
1392
|
|
|
/** |
1393
|
|
|
* Validate and normalize of parameters of type 'user' |
1394
|
|
|
* @param string $value Parameter value |
1395
|
|
|
* @param string $encParamName Parameter name |
1396
|
|
|
* @return string Validated and normalized parameter |
1397
|
|
|
*/ |
1398
|
|
|
private function validateUser( $value, $encParamName ) { |
1399
|
|
|
$title = Title::makeTitleSafe( NS_USER, $value ); |
1400
|
|
|
if ( $title === null || $title->hasFragment() ) { |
1401
|
|
|
$this->dieUsage( |
1402
|
|
|
"Invalid value '$value' for user parameter $encParamName", |
1403
|
|
|
"baduser_{$encParamName}" |
1404
|
|
|
); |
1405
|
|
|
} |
1406
|
|
|
|
1407
|
|
|
return $title->getText(); |
1408
|
|
|
} |
1409
|
|
|
|
1410
|
|
|
/**@}*/ |
1411
|
|
|
|
1412
|
|
|
/************************************************************************//** |
1413
|
|
|
* @name Utility methods |
1414
|
|
|
* @{ |
1415
|
|
|
*/ |
1416
|
|
|
|
1417
|
|
|
/** |
1418
|
|
|
* Set a watch (or unwatch) based the based on a watchlist parameter. |
1419
|
|
|
* @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange' |
1420
|
|
|
* @param Title $titleObj The article's title to change |
1421
|
|
|
* @param string $userOption The user option to consider when $watch=preferences |
1422
|
|
|
*/ |
1423
|
|
|
protected function setWatch( $watch, $titleObj, $userOption = null ) { |
1424
|
|
|
$value = $this->getWatchlistValue( $watch, $titleObj, $userOption ); |
1425
|
|
|
if ( $value === null ) { |
1426
|
|
|
return; |
1427
|
|
|
} |
1428
|
|
|
|
1429
|
|
|
WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() ); |
1430
|
|
|
} |
1431
|
|
|
|
1432
|
|
|
/** |
1433
|
|
|
* Truncate an array to a certain length. |
1434
|
|
|
* @param array $arr Array to truncate |
1435
|
|
|
* @param int $limit Maximum length |
1436
|
|
|
* @return bool True if the array was truncated, false otherwise |
1437
|
|
|
*/ |
1438
|
|
|
public static function truncateArray( &$arr, $limit ) { |
1439
|
|
|
$modified = false; |
1440
|
|
|
while ( count( $arr ) > $limit ) { |
1441
|
|
|
array_pop( $arr ); |
1442
|
|
|
$modified = true; |
1443
|
|
|
} |
1444
|
|
|
|
1445
|
|
|
return $modified; |
1446
|
|
|
} |
1447
|
|
|
|
1448
|
|
|
/** |
1449
|
|
|
* Gets the user for whom to get the watchlist |
1450
|
|
|
* |
1451
|
|
|
* @param array $params |
1452
|
|
|
* @return User |
1453
|
|
|
*/ |
1454
|
|
|
public function getWatchlistUser( $params ) { |
1455
|
|
|
if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) { |
1456
|
|
|
$user = User::newFromName( $params['owner'], false ); |
1457
|
|
|
if ( !( $user && $user->getId() ) ) { |
1458
|
|
|
$this->dieUsage( 'Specified user does not exist', 'bad_wlowner' ); |
1459
|
|
|
} |
1460
|
|
|
$token = $user->getOption( 'watchlisttoken' ); |
1461
|
|
|
if ( $token == '' || !hash_equals( $token, $params['token'] ) ) { |
1462
|
|
|
$this->dieUsage( |
1463
|
|
|
'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', |
1464
|
|
|
'bad_wltoken' |
1465
|
|
|
); |
1466
|
|
|
} |
1467
|
|
|
} else { |
1468
|
|
|
if ( !$this->getUser()->isLoggedIn() ) { |
1469
|
|
|
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' ); |
1470
|
|
|
} |
1471
|
|
|
if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) { |
1472
|
|
|
$this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' ); |
1473
|
|
|
} |
1474
|
|
|
$user = $this->getUser(); |
1475
|
|
|
} |
1476
|
|
|
|
1477
|
|
|
return $user; |
1478
|
|
|
} |
1479
|
|
|
|
1480
|
|
|
/** |
1481
|
|
|
* A subset of wfEscapeWikiText for BC texts |
1482
|
|
|
* |
1483
|
|
|
* @since 1.25 |
1484
|
|
|
* @param string|array $v |
1485
|
|
|
* @return string|array |
1486
|
|
|
*/ |
1487
|
|
View Code Duplication |
private static function escapeWikiText( $v ) { |
1488
|
|
|
if ( is_array( $v ) ) { |
1489
|
|
|
return array_map( 'self::escapeWikiText', $v ); |
1490
|
|
|
} else { |
1491
|
|
|
return strtr( $v, [ |
1492
|
|
|
'__' => '__', '{' => '{', '}' => '}', |
1493
|
|
|
'[[Category:' => '[[:Category:', |
1494
|
|
|
'[[File:' => '[[:File:', '[[Image:' => '[[:Image:', |
1495
|
|
|
] ); |
1496
|
|
|
} |
1497
|
|
|
} |
1498
|
|
|
|
1499
|
|
|
/** |
1500
|
|
|
* Create a Message from a string or array |
1501
|
|
|
* |
1502
|
|
|
* A string is used as a message key. An array has the message key as the |
1503
|
|
|
* first value and message parameters as subsequent values. |
1504
|
|
|
* |
1505
|
|
|
* @since 1.25 |
1506
|
|
|
* @param string|array|Message $msg |
1507
|
|
|
* @param IContextSource $context |
1508
|
|
|
* @param array $params |
1509
|
|
|
* @return Message|null |
1510
|
|
|
*/ |
1511
|
|
|
public static function makeMessage( $msg, IContextSource $context, array $params = null ) { |
1512
|
|
|
if ( is_string( $msg ) ) { |
1513
|
|
|
$msg = wfMessage( $msg ); |
1514
|
|
|
} elseif ( is_array( $msg ) ) { |
1515
|
|
|
$msg = call_user_func_array( 'wfMessage', $msg ); |
1516
|
|
|
} |
1517
|
|
|
if ( !$msg instanceof Message ) { |
1518
|
|
|
return null; |
1519
|
|
|
} |
1520
|
|
|
|
1521
|
|
|
$msg->setContext( $context ); |
1522
|
|
|
if ( $params ) { |
1523
|
|
|
$msg->params( $params ); |
1524
|
|
|
} |
1525
|
|
|
|
1526
|
|
|
return $msg; |
1527
|
|
|
} |
1528
|
|
|
|
1529
|
|
|
/**@}*/ |
1530
|
|
|
|
1531
|
|
|
/************************************************************************//** |
1532
|
|
|
* @name Warning and error reporting |
1533
|
|
|
* @{ |
1534
|
|
|
*/ |
1535
|
|
|
|
1536
|
|
|
/** |
1537
|
|
|
* Set warning section for this module. Users should monitor this |
1538
|
|
|
* section to notice any changes in API. Multiple calls to this |
1539
|
|
|
* function will result in the warning messages being separated by |
1540
|
|
|
* newlines |
1541
|
|
|
* @param string $warning Warning message |
1542
|
|
|
*/ |
1543
|
|
|
public function setWarning( $warning ) { |
1544
|
|
|
$msg = new ApiRawMessage( $warning, 'warning' ); |
1545
|
|
|
$this->getErrorFormatter()->addWarning( $this->getModuleName(), $msg ); |
1546
|
|
|
} |
1547
|
|
|
|
1548
|
|
|
/** |
1549
|
|
|
* Adds a warning to the output, else dies |
1550
|
|
|
* |
1551
|
|
|
* @param string $msg Message to show as a warning, or error message if dying |
1552
|
|
|
* @param bool $enforceLimits Whether this is an enforce (die) |
1553
|
|
|
*/ |
1554
|
|
|
private function warnOrDie( $msg, $enforceLimits = false ) { |
1555
|
|
|
if ( $enforceLimits ) { |
1556
|
|
|
$this->dieUsage( $msg, 'integeroutofrange' ); |
1557
|
|
|
} |
1558
|
|
|
|
1559
|
|
|
$this->setWarning( $msg ); |
1560
|
|
|
} |
1561
|
|
|
|
1562
|
|
|
/** |
1563
|
|
|
* Throw a UsageException, which will (if uncaught) call the main module's |
1564
|
|
|
* error handler and die with an error message. |
1565
|
|
|
* |
1566
|
|
|
* @param string $description One-line human-readable description of the |
1567
|
|
|
* error condition, e.g., "The API requires a valid action parameter" |
1568
|
|
|
* @param string $errorCode Brief, arbitrary, stable string to allow easy |
1569
|
|
|
* automated identification of the error, e.g., 'unknown_action' |
1570
|
|
|
* @param int $httpRespCode HTTP response code |
1571
|
|
|
* @param array|null $extradata Data to add to the "<error>" element; array in ApiResult format |
1572
|
|
|
* @throws UsageException always |
1573
|
|
|
*/ |
1574
|
|
|
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) { |
1575
|
|
|
throw new UsageException( |
1576
|
|
|
$description, |
1577
|
|
|
$this->encodeParamName( $errorCode ), |
1578
|
|
|
$httpRespCode, |
1579
|
|
|
$extradata |
1580
|
|
|
); |
1581
|
|
|
} |
1582
|
|
|
|
1583
|
|
|
/** |
1584
|
|
|
* Throw a UsageException, which will (if uncaught) call the main module's |
1585
|
|
|
* error handler and die with an error message including block info. |
1586
|
|
|
* |
1587
|
|
|
* @since 1.27 |
1588
|
|
|
* @param Block $block The block used to generate the UsageException |
1589
|
|
|
* @throws UsageException always |
1590
|
|
|
*/ |
1591
|
|
|
public function dieBlocked( Block $block ) { |
1592
|
|
|
// Die using the appropriate message depending on block type |
1593
|
|
|
if ( $block->getType() == Block::TYPE_AUTO ) { |
1594
|
|
|
$this->dieUsage( |
1595
|
|
|
'Your IP address has been blocked automatically, because it was used by a blocked user', |
1596
|
|
|
'autoblocked', |
1597
|
|
|
0, |
1598
|
|
|
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ] |
1599
|
|
|
); |
1600
|
|
|
} else { |
1601
|
|
|
$this->dieUsage( |
1602
|
|
|
'You have been blocked from editing', |
1603
|
|
|
'blocked', |
1604
|
|
|
0, |
1605
|
|
|
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ] |
1606
|
|
|
); |
1607
|
|
|
} |
1608
|
|
|
} |
1609
|
|
|
|
1610
|
|
|
/** |
1611
|
|
|
* Get error (as code, string) from a Status object. |
1612
|
|
|
* |
1613
|
|
|
* @since 1.23 |
1614
|
|
|
* @param Status $status |
1615
|
|
|
* @param array|null &$extraData Set if extra data from IApiMessage is available (since 1.27) |
1616
|
|
|
* @return array Array of code and error string |
1617
|
|
|
* @throws MWException |
1618
|
|
|
*/ |
1619
|
|
|
public function getErrorFromStatus( $status, &$extraData = null ) { |
1620
|
|
|
if ( $status->isGood() ) { |
1621
|
|
|
throw new MWException( 'Successful status passed to ApiBase::dieStatus' ); |
1622
|
|
|
} |
1623
|
|
|
|
1624
|
|
|
$errors = $status->getErrorsByType( 'error' ); |
1625
|
|
|
if ( !$errors ) { |
1626
|
|
|
// No errors? Assume the warnings should be treated as errors |
1627
|
|
|
$errors = $status->getErrorsByType( 'warning' ); |
1628
|
|
|
} |
1629
|
|
|
if ( !$errors ) { |
1630
|
|
|
// Still no errors? Punt |
1631
|
|
|
$errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ]; |
1632
|
|
|
} |
1633
|
|
|
|
1634
|
|
|
// Cannot use dieUsageMsg() because extensions might return custom |
1635
|
|
|
// error messages. |
1636
|
|
|
if ( $errors[0]['message'] instanceof Message ) { |
1637
|
|
|
$msg = $errors[0]['message']; |
1638
|
|
|
if ( $msg instanceof IApiMessage ) { |
1639
|
|
|
$extraData = $msg->getApiData(); |
1640
|
|
|
$code = $msg->getApiCode(); |
1641
|
|
|
} else { |
1642
|
|
|
$code = $msg->getKey(); |
1643
|
|
|
} |
1644
|
|
|
} else { |
1645
|
|
|
$code = $errors[0]['message']; |
1646
|
|
|
$msg = wfMessage( $code, $errors[0]['params'] ); |
1647
|
|
|
} |
1648
|
|
|
if ( isset( ApiBase::$messageMap[$code] ) ) { |
1649
|
|
|
// Translate message to code, for backwards compatibility |
1650
|
|
|
$code = ApiBase::$messageMap[$code]['code']; |
1651
|
|
|
} |
1652
|
|
|
|
1653
|
|
|
return [ $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() ]; |
1654
|
|
|
} |
1655
|
|
|
|
1656
|
|
|
/** |
1657
|
|
|
* Throw a UsageException based on the errors in the Status object. |
1658
|
|
|
* |
1659
|
|
|
* @since 1.22 |
1660
|
|
|
* @param Status $status |
1661
|
|
|
* @throws UsageException always |
1662
|
|
|
*/ |
1663
|
|
|
public function dieStatus( $status ) { |
1664
|
|
|
$extraData = null; |
1665
|
|
|
list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData ); |
1666
|
|
|
$this->dieUsage( $msg, $code, 0, $extraData ); |
1667
|
|
|
} |
1668
|
|
|
|
1669
|
|
|
// @codingStandardsIgnoreStart Allow long lines. Cannot split these. |
1670
|
|
|
/** |
1671
|
|
|
* Array that maps message keys to error messages. $1 and friends are replaced. |
1672
|
|
|
*/ |
1673
|
|
|
public static $messageMap = [ |
1674
|
|
|
// This one MUST be present, or dieUsageMsg() will recurse infinitely |
1675
|
|
|
'unknownerror' => [ 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ], |
1676
|
|
|
'unknownerror-nocode' => [ 'code' => 'unknownerror', 'info' => 'Unknown error' ], |
1677
|
|
|
|
1678
|
|
|
// Messages from Title::getUserPermissionsErrors() |
1679
|
|
|
'ns-specialprotected' => [ |
1680
|
|
|
'code' => 'unsupportednamespace', |
1681
|
|
|
'info' => "Pages in the Special namespace can't be edited" |
1682
|
|
|
], |
1683
|
|
|
'protectedinterface' => [ |
1684
|
|
|
'code' => 'protectednamespace-interface', |
1685
|
|
|
'info' => "You're not allowed to edit interface messages" |
1686
|
|
|
], |
1687
|
|
|
'namespaceprotected' => [ |
1688
|
|
|
'code' => 'protectednamespace', |
1689
|
|
|
'info' => "You're not allowed to edit pages in the \"\$1\" namespace" |
1690
|
|
|
], |
1691
|
|
|
'customcssprotected' => [ |
1692
|
|
|
'code' => 'customcssprotected', |
1693
|
|
|
'info' => "You're not allowed to edit custom CSS pages" |
1694
|
|
|
], |
1695
|
|
|
'customjsprotected' => [ |
1696
|
|
|
'code' => 'customjsprotected', |
1697
|
|
|
'info' => "You're not allowed to edit custom JavaScript pages" |
1698
|
|
|
], |
1699
|
|
|
'cascadeprotected' => [ |
1700
|
|
|
'code' => 'cascadeprotected', |
1701
|
|
|
'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" |
1702
|
|
|
], |
1703
|
|
|
'protectedpagetext' => [ |
1704
|
|
|
'code' => 'protectedpage', |
1705
|
|
|
'info' => "The \"\$1\" right is required to edit this page" |
1706
|
|
|
], |
1707
|
|
|
'protect-cantedit' => [ |
1708
|
|
|
'code' => 'cantedit', |
1709
|
|
|
'info' => "You can't protect this page because you can't edit it" |
1710
|
|
|
], |
1711
|
|
|
'deleteprotected' => [ |
1712
|
|
|
'code' => 'cantedit', |
1713
|
|
|
'info' => "You can't delete this page because it has been protected" |
1714
|
|
|
], |
1715
|
|
|
'badaccess-group0' => [ |
1716
|
|
|
'code' => 'permissiondenied', |
1717
|
|
|
'info' => 'Permission denied' |
1718
|
|
|
], // Generic permission denied message |
1719
|
|
|
'badaccess-groups' => [ |
1720
|
|
|
'code' => 'permissiondenied', |
1721
|
|
|
'info' => 'Permission denied' |
1722
|
|
|
], |
1723
|
|
|
'titleprotected' => [ |
1724
|
|
|
'code' => 'protectedtitle', |
1725
|
|
|
'info' => 'This title has been protected from creation' |
1726
|
|
|
], |
1727
|
|
|
'nocreate-loggedin' => [ |
1728
|
|
|
'code' => 'cantcreate', |
1729
|
|
|
'info' => "You don't have permission to create new pages" |
1730
|
|
|
], |
1731
|
|
|
'nocreatetext' => [ |
1732
|
|
|
'code' => 'cantcreate-anon', |
1733
|
|
|
'info' => "Anonymous users can't create new pages" |
1734
|
|
|
], |
1735
|
|
|
'movenologintext' => [ |
1736
|
|
|
'code' => 'cantmove-anon', |
1737
|
|
|
'info' => "Anonymous users can't move pages" |
1738
|
|
|
], |
1739
|
|
|
'movenotallowed' => [ |
1740
|
|
|
'code' => 'cantmove', |
1741
|
|
|
'info' => "You don't have permission to move pages" |
1742
|
|
|
], |
1743
|
|
|
'confirmedittext' => [ |
1744
|
|
|
'code' => 'confirmemail', |
1745
|
|
|
'info' => 'You must confirm your email address before you can edit' |
1746
|
|
|
], |
1747
|
|
|
'blockedtext' => [ |
1748
|
|
|
'code' => 'blocked', |
1749
|
|
|
'info' => 'You have been blocked from editing' |
1750
|
|
|
], |
1751
|
|
|
'autoblockedtext' => [ |
1752
|
|
|
'code' => 'autoblocked', |
1753
|
|
|
'info' => 'Your IP address has been blocked automatically, because it was used by a blocked user' |
1754
|
|
|
], |
1755
|
|
|
|
1756
|
|
|
// Miscellaneous interface messages |
1757
|
|
|
'actionthrottledtext' => [ |
1758
|
|
|
'code' => 'ratelimited', |
1759
|
|
|
'info' => "You've exceeded your rate limit. Please wait some time and try again" |
1760
|
|
|
], |
1761
|
|
|
'alreadyrolled' => [ |
1762
|
|
|
'code' => 'alreadyrolled', |
1763
|
|
|
'info' => 'The page you tried to rollback was already rolled back' |
1764
|
|
|
], |
1765
|
|
|
'cantrollback' => [ |
1766
|
|
|
'code' => 'onlyauthor', |
1767
|
|
|
'info' => 'The page you tried to rollback only has one author' |
1768
|
|
|
], |
1769
|
|
|
'readonlytext' => [ |
1770
|
|
|
'code' => 'readonly', |
1771
|
|
|
'info' => 'The wiki is currently in read-only mode' |
1772
|
|
|
], |
1773
|
|
|
'sessionfailure' => [ |
1774
|
|
|
'code' => 'badtoken', |
1775
|
|
|
'info' => 'Invalid token' ], |
1776
|
|
|
'cannotdelete' => [ |
1777
|
|
|
'code' => 'cantdelete', |
1778
|
|
|
'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else" |
1779
|
|
|
], |
1780
|
|
|
'notanarticle' => [ |
1781
|
|
|
'code' => 'missingtitle', |
1782
|
|
|
'info' => "The page you requested doesn't exist" |
1783
|
|
|
], |
1784
|
|
|
'selfmove' => [ 'code' => 'selfmove', 'info' => "Can't move a page to itself" |
1785
|
|
|
], |
1786
|
|
|
'immobile_namespace' => [ |
1787
|
|
|
'code' => 'immobilenamespace', |
1788
|
|
|
'info' => 'You tried to move pages from or to a namespace that is protected from moving' |
1789
|
|
|
], |
1790
|
|
|
'articleexists' => [ |
1791
|
|
|
'code' => 'articleexists', |
1792
|
|
|
'info' => 'The destination article already exists and is not a redirect to the source article' |
1793
|
|
|
], |
1794
|
|
|
'protectedpage' => [ |
1795
|
|
|
'code' => 'protectedpage', |
1796
|
|
|
'info' => "You don't have permission to perform this move" |
1797
|
|
|
], |
1798
|
|
|
'hookaborted' => [ |
1799
|
|
|
'code' => 'hookaborted', |
1800
|
|
|
'info' => 'The modification you tried to make was aborted by an extension hook' |
1801
|
|
|
], |
1802
|
|
|
'cantmove-titleprotected' => [ |
1803
|
|
|
'code' => 'protectedtitle', |
1804
|
|
|
'info' => 'The destination article has been protected from creation' |
1805
|
|
|
], |
1806
|
|
|
'imagenocrossnamespace' => [ |
1807
|
|
|
'code' => 'nonfilenamespace', |
1808
|
|
|
'info' => "Can't move a file to a non-file namespace" |
1809
|
|
|
], |
1810
|
|
|
'imagetypemismatch' => [ |
1811
|
|
|
'code' => 'filetypemismatch', |
1812
|
|
|
'info' => "The new file extension doesn't match its type" |
1813
|
|
|
], |
1814
|
|
|
// 'badarticleerror' => shouldn't happen |
1815
|
|
|
// 'badtitletext' => shouldn't happen |
1816
|
|
|
'ip_range_invalid' => [ 'code' => 'invalidrange', 'info' => 'Invalid IP range' ], |
1817
|
|
|
'range_block_disabled' => [ |
1818
|
|
|
'code' => 'rangedisabled', |
1819
|
|
|
'info' => 'Blocking IP ranges has been disabled' |
1820
|
|
|
], |
1821
|
|
|
'nosuchusershort' => [ |
1822
|
|
|
'code' => 'nosuchuser', |
1823
|
|
|
'info' => "The user you specified doesn't exist" |
1824
|
|
|
], |
1825
|
|
|
'badipaddress' => [ 'code' => 'invalidip', 'info' => 'Invalid IP address specified' ], |
1826
|
|
|
'ipb_expiry_invalid' => [ 'code' => 'invalidexpiry', 'info' => 'Invalid expiry time' ], |
1827
|
|
|
'ipb_already_blocked' => [ |
1828
|
|
|
'code' => 'alreadyblocked', |
1829
|
|
|
'info' => 'The user you tried to block was already blocked' |
1830
|
|
|
], |
1831
|
|
|
'ipb_blocked_as_range' => [ |
1832
|
|
|
'code' => 'blockedasrange', |
1833
|
|
|
'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." |
1834
|
|
|
], |
1835
|
|
|
'ipb_cant_unblock' => [ |
1836
|
|
|
'code' => 'cantunblock', |
1837
|
|
|
'info' => 'The block you specified was not found. It may have been unblocked already' |
1838
|
|
|
], |
1839
|
|
|
'mailnologin' => [ |
1840
|
|
|
'code' => 'cantsend', |
1841
|
|
|
'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' |
1842
|
|
|
], |
1843
|
|
|
'ipbblocked' => [ |
1844
|
|
|
'code' => 'ipbblocked', |
1845
|
|
|
'info' => 'You cannot block or unblock users while you are yourself blocked' |
1846
|
|
|
], |
1847
|
|
|
'ipbnounblockself' => [ |
1848
|
|
|
'code' => 'ipbnounblockself', |
1849
|
|
|
'info' => 'You are not allowed to unblock yourself' |
1850
|
|
|
], |
1851
|
|
|
'usermaildisabled' => [ |
1852
|
|
|
'code' => 'usermaildisabled', |
1853
|
|
|
'info' => 'User email has been disabled' |
1854
|
|
|
], |
1855
|
|
|
'blockedemailuser' => [ |
1856
|
|
|
'code' => 'blockedfrommail', |
1857
|
|
|
'info' => 'You have been blocked from sending email' |
1858
|
|
|
], |
1859
|
|
|
'notarget' => [ |
1860
|
|
|
'code' => 'notarget', |
1861
|
|
|
'info' => 'You have not specified a valid target for this action' |
1862
|
|
|
], |
1863
|
|
|
'noemail' => [ |
1864
|
|
|
'code' => 'noemail', |
1865
|
|
|
'info' => 'The user has not specified a valid email address, or has chosen not to receive email from other users' |
1866
|
|
|
], |
1867
|
|
|
'rcpatroldisabled' => [ |
1868
|
|
|
'code' => 'patroldisabled', |
1869
|
|
|
'info' => 'Patrolling is disabled on this wiki' |
1870
|
|
|
], |
1871
|
|
|
'markedaspatrollederror-noautopatrol' => [ |
1872
|
|
|
'code' => 'noautopatrol', |
1873
|
|
|
'info' => "You don't have permission to patrol your own changes" |
1874
|
|
|
], |
1875
|
|
|
'delete-toobig' => [ |
1876
|
|
|
'code' => 'bigdelete', |
1877
|
|
|
'info' => "You can't delete this page because it has more than \$1 revisions" |
1878
|
|
|
], |
1879
|
|
|
'movenotallowedfile' => [ |
1880
|
|
|
'code' => 'cantmovefile', |
1881
|
|
|
'info' => "You don't have permission to move files" |
1882
|
|
|
], |
1883
|
|
|
'userrights-no-interwiki' => [ |
1884
|
|
|
'code' => 'nointerwikiuserrights', |
1885
|
|
|
'info' => "You don't have permission to change user rights on other wikis" |
1886
|
|
|
], |
1887
|
|
|
'userrights-nodatabase' => [ |
1888
|
|
|
'code' => 'nosuchdatabase', |
1889
|
|
|
'info' => "Database \"\$1\" does not exist or is not local" |
1890
|
|
|
], |
1891
|
|
|
'nouserspecified' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ], |
1892
|
|
|
'noname' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ], |
1893
|
|
|
'summaryrequired' => [ 'code' => 'summaryrequired', 'info' => 'Summary required' ], |
1894
|
|
|
'import-rootpage-invalid' => [ |
1895
|
|
|
'code' => 'import-rootpage-invalid', |
1896
|
|
|
'info' => 'Root page is an invalid title' |
1897
|
|
|
], |
1898
|
|
|
'import-rootpage-nosubpage' => [ |
1899
|
|
|
'code' => 'import-rootpage-nosubpage', |
1900
|
|
|
'info' => 'Namespace "$1" of the root page does not allow subpages' |
1901
|
|
|
], |
1902
|
|
|
|
1903
|
|
|
// API-specific messages |
1904
|
|
|
'readrequired' => [ |
1905
|
|
|
'code' => 'readapidenied', |
1906
|
|
|
'info' => 'You need read permission to use this module' |
1907
|
|
|
], |
1908
|
|
|
'writedisabled' => [ |
1909
|
|
|
'code' => 'noapiwrite', |
1910
|
|
|
'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" |
1911
|
|
|
], |
1912
|
|
|
'writerequired' => [ |
1913
|
|
|
'code' => 'writeapidenied', |
1914
|
|
|
'info' => "You're not allowed to edit this wiki through the API" |
1915
|
|
|
], |
1916
|
|
|
'missingparam' => [ 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ], |
1917
|
|
|
'invalidtitle' => [ 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ], |
1918
|
|
|
'nosuchpageid' => [ 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ], |
1919
|
|
|
'nosuchrevid' => [ 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ], |
1920
|
|
|
'nosuchuser' => [ 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ], |
1921
|
|
|
'invaliduser' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ], |
1922
|
|
|
'invalidexpiry' => [ 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ], |
1923
|
|
|
'pastexpiry' => [ 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ], |
1924
|
|
|
'create-titleexists' => [ |
1925
|
|
|
'code' => 'create-titleexists', |
1926
|
|
|
'info' => "Existing titles can't be protected with 'create'" |
1927
|
|
|
], |
1928
|
|
|
'missingtitle-createonly' => [ |
1929
|
|
|
'code' => 'missingtitle-createonly', |
1930
|
|
|
'info' => "Missing titles can only be protected with 'create'" |
1931
|
|
|
], |
1932
|
|
|
'cantblock' => [ 'code' => 'cantblock', |
1933
|
|
|
'info' => "You don't have permission to block users" |
1934
|
|
|
], |
1935
|
|
|
'canthide' => [ |
1936
|
|
|
'code' => 'canthide', |
1937
|
|
|
'info' => "You don't have permission to hide user names from the block log" |
1938
|
|
|
], |
1939
|
|
|
'cantblock-email' => [ |
1940
|
|
|
'code' => 'cantblock-email', |
1941
|
|
|
'info' => "You don't have permission to block users from sending email through the wiki" |
1942
|
|
|
], |
1943
|
|
|
'unblock-notarget' => [ |
1944
|
|
|
'code' => 'notarget', |
1945
|
|
|
'info' => 'Either the id or the user parameter must be set' |
1946
|
|
|
], |
1947
|
|
|
'unblock-idanduser' => [ |
1948
|
|
|
'code' => 'idanduser', |
1949
|
|
|
'info' => "The id and user parameters can't be used together" |
1950
|
|
|
], |
1951
|
|
|
'cantunblock' => [ |
1952
|
|
|
'code' => 'permissiondenied', |
1953
|
|
|
'info' => "You don't have permission to unblock users" |
1954
|
|
|
], |
1955
|
|
|
'cannotundelete' => [ |
1956
|
|
|
'code' => 'cantundelete', |
1957
|
|
|
'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already" |
1958
|
|
|
], |
1959
|
|
|
'permdenied-undelete' => [ |
1960
|
|
|
'code' => 'permissiondenied', |
1961
|
|
|
'info' => "You don't have permission to restore deleted revisions" |
1962
|
|
|
], |
1963
|
|
|
'createonly-exists' => [ |
1964
|
|
|
'code' => 'articleexists', |
1965
|
|
|
'info' => 'The article you tried to create has been created already' |
1966
|
|
|
], |
1967
|
|
|
'nocreate-missing' => [ |
1968
|
|
|
'code' => 'missingtitle', |
1969
|
|
|
'info' => "The article you tried to edit doesn't exist" |
1970
|
|
|
], |
1971
|
|
|
'cantchangecontentmodel' => [ |
1972
|
|
|
'code' => 'cantchangecontentmodel', |
1973
|
|
|
'info' => "You don't have permission to change the content model of a page" |
1974
|
|
|
], |
1975
|
|
|
'nosuchrcid' => [ |
1976
|
|
|
'code' => 'nosuchrcid', |
1977
|
|
|
'info' => "There is no change with rcid \"\$1\"" |
1978
|
|
|
], |
1979
|
|
|
'nosuchlogid' => [ |
1980
|
|
|
'code' => 'nosuchlogid', |
1981
|
|
|
'info' => "There is no log entry with ID \"\$1\"" |
1982
|
|
|
], |
1983
|
|
|
'protect-invalidaction' => [ |
1984
|
|
|
'code' => 'protect-invalidaction', |
1985
|
|
|
'info' => "Invalid protection type \"\$1\"" |
1986
|
|
|
], |
1987
|
|
|
'protect-invalidlevel' => [ |
1988
|
|
|
'code' => 'protect-invalidlevel', |
1989
|
|
|
'info' => "Invalid protection level \"\$1\"" |
1990
|
|
|
], |
1991
|
|
|
'toofewexpiries' => [ |
1992
|
|
|
'code' => 'toofewexpiries', |
1993
|
|
|
'info' => "\$1 expiry timestamps were provided where \$2 were needed" |
1994
|
|
|
], |
1995
|
|
|
'cantimport' => [ |
1996
|
|
|
'code' => 'cantimport', |
1997
|
|
|
'info' => "You don't have permission to import pages" |
1998
|
|
|
], |
1999
|
|
|
'cantimport-upload' => [ |
2000
|
|
|
'code' => 'cantimport-upload', |
2001
|
|
|
'info' => "You don't have permission to import uploaded pages" |
2002
|
|
|
], |
2003
|
|
|
'importnofile' => [ 'code' => 'nofile', 'info' => "You didn't upload a file" ], |
2004
|
|
|
'importuploaderrorsize' => [ |
2005
|
|
|
'code' => 'filetoobig', |
2006
|
|
|
'info' => 'The file you uploaded is bigger than the maximum upload size' |
2007
|
|
|
], |
2008
|
|
|
'importuploaderrorpartial' => [ |
2009
|
|
|
'code' => 'partialupload', |
2010
|
|
|
'info' => 'The file was only partially uploaded' |
2011
|
|
|
], |
2012
|
|
|
'importuploaderrortemp' => [ |
2013
|
|
|
'code' => 'notempdir', |
2014
|
|
|
'info' => 'The temporary upload directory is missing' |
2015
|
|
|
], |
2016
|
|
|
'importcantopen' => [ |
2017
|
|
|
'code' => 'cantopenfile', |
2018
|
|
|
'info' => "Couldn't open the uploaded file" |
2019
|
|
|
], |
2020
|
|
|
'import-noarticle' => [ |
2021
|
|
|
'code' => 'badinterwiki', |
2022
|
|
|
'info' => 'Invalid interwiki title specified' |
2023
|
|
|
], |
2024
|
|
|
'importbadinterwiki' => [ |
2025
|
|
|
'code' => 'badinterwiki', |
2026
|
|
|
'info' => 'Invalid interwiki title specified' |
2027
|
|
|
], |
2028
|
|
|
'import-unknownerror' => [ |
2029
|
|
|
'code' => 'import-unknownerror', |
2030
|
|
|
'info' => "Unknown error on import: \"\$1\"" |
2031
|
|
|
], |
2032
|
|
|
'cantoverwrite-sharedfile' => [ |
2033
|
|
|
'code' => 'cantoverwrite-sharedfile', |
2034
|
|
|
'info' => 'The target file exists on a shared repository and you do not have permission to override it' |
2035
|
|
|
], |
2036
|
|
|
'sharedfile-exists' => [ |
2037
|
|
|
'code' => 'fileexists-sharedrepo-perm', |
2038
|
|
|
'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' |
2039
|
|
|
], |
2040
|
|
|
'mustbeposted' => [ |
2041
|
|
|
'code' => 'mustbeposted', |
2042
|
|
|
'info' => "The \$1 module requires a POST request" |
2043
|
|
|
], |
2044
|
|
|
'show' => [ |
2045
|
|
|
'code' => 'show', |
2046
|
|
|
'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' |
2047
|
|
|
], |
2048
|
|
|
'specialpage-cantexecute' => [ |
2049
|
|
|
'code' => 'specialpage-cantexecute', |
2050
|
|
|
'info' => "You don't have permission to view the results of this special page" |
2051
|
|
|
], |
2052
|
|
|
'invalidoldimage' => [ |
2053
|
|
|
'code' => 'invalidoldimage', |
2054
|
|
|
'info' => 'The oldimage parameter has invalid format' |
2055
|
|
|
], |
2056
|
|
|
'nodeleteablefile' => [ |
2057
|
|
|
'code' => 'nodeleteablefile', |
2058
|
|
|
'info' => 'No such old version of the file' |
2059
|
|
|
], |
2060
|
|
|
'fileexists-forbidden' => [ |
2061
|
|
|
'code' => 'fileexists-forbidden', |
2062
|
|
|
'info' => 'A file with name "$1" already exists, and cannot be overwritten.' |
2063
|
|
|
], |
2064
|
|
|
'fileexists-shared-forbidden' => [ |
2065
|
|
|
'code' => 'fileexists-shared-forbidden', |
2066
|
|
|
'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.' |
2067
|
|
|
], |
2068
|
|
|
'filerevert-badversion' => [ |
2069
|
|
|
'code' => 'filerevert-badversion', |
2070
|
|
|
'info' => 'There is no previous local version of this file with the provided timestamp.' |
2071
|
|
|
], |
2072
|
|
|
|
2073
|
|
|
// ApiEditPage messages |
2074
|
|
|
'noimageredirect-anon' => [ |
2075
|
|
|
'code' => 'noimageredirect-anon', |
2076
|
|
|
'info' => "Anonymous users can't create image redirects" |
2077
|
|
|
], |
2078
|
|
|
'noimageredirect-logged' => [ |
2079
|
|
|
'code' => 'noimageredirect', |
2080
|
|
|
'info' => "You don't have permission to create image redirects" |
2081
|
|
|
], |
2082
|
|
|
'spamdetected' => [ |
2083
|
|
|
'code' => 'spamdetected', |
2084
|
|
|
'info' => "Your edit was refused because it contained a spam fragment: \"\$1\"" |
2085
|
|
|
], |
2086
|
|
|
'contenttoobig' => [ |
2087
|
|
|
'code' => 'contenttoobig', |
2088
|
|
|
'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" |
2089
|
|
|
], |
2090
|
|
|
'noedit-anon' => [ 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ], |
2091
|
|
|
'noedit' => [ 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ], |
2092
|
|
|
'wasdeleted' => [ |
2093
|
|
|
'code' => 'pagedeleted', |
2094
|
|
|
'info' => 'The page has been deleted since you fetched its timestamp' |
2095
|
|
|
], |
2096
|
|
|
'blankpage' => [ |
2097
|
|
|
'code' => 'emptypage', |
2098
|
|
|
'info' => 'Creating new, empty pages is not allowed' |
2099
|
|
|
], |
2100
|
|
|
'editconflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ], |
2101
|
|
|
'hashcheckfailed' => [ 'code' => 'badmd5', 'info' => 'The supplied MD5 hash was incorrect' ], |
2102
|
|
|
'missingtext' => [ |
2103
|
|
|
'code' => 'notext', |
2104
|
|
|
'info' => 'One of the text, appendtext, prependtext and undo parameters must be set' |
2105
|
|
|
], |
2106
|
|
|
'emptynewsection' => [ |
2107
|
|
|
'code' => 'emptynewsection', |
2108
|
|
|
'info' => 'Creating empty new sections is not possible.' |
2109
|
|
|
], |
2110
|
|
|
'revwrongpage' => [ |
2111
|
|
|
'code' => 'revwrongpage', |
2112
|
|
|
'info' => "r\$1 is not a revision of \"\$2\"" |
2113
|
|
|
], |
2114
|
|
|
'undo-failure' => [ |
2115
|
|
|
'code' => 'undofailure', |
2116
|
|
|
'info' => 'Undo failed due to conflicting intermediate edits' |
2117
|
|
|
], |
2118
|
|
|
'content-not-allowed-here' => [ |
2119
|
|
|
'code' => 'contentnotallowedhere', |
2120
|
|
|
'info' => 'Content model "$1" is not allowed at title "$2"' |
2121
|
|
|
], |
2122
|
|
|
|
2123
|
|
|
// Messages from WikiPage::doEit(] |
2124
|
|
|
'edit-hook-aborted' => [ |
2125
|
|
|
'code' => 'edit-hook-aborted', |
2126
|
|
|
'info' => 'Your edit was aborted by an ArticleSave hook' |
2127
|
|
|
], |
2128
|
|
|
'edit-gone-missing' => [ |
2129
|
|
|
'code' => 'edit-gone-missing', |
2130
|
|
|
'info' => "The page you tried to edit doesn't seem to exist anymore" |
2131
|
|
|
], |
2132
|
|
|
'edit-conflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ], |
2133
|
|
|
'edit-already-exists' => [ |
2134
|
|
|
'code' => 'edit-already-exists', |
2135
|
|
|
'info' => 'It seems the page you tried to create already exist' |
2136
|
|
|
], |
2137
|
|
|
|
2138
|
|
|
// uploadMsgs |
2139
|
|
|
'invalid-file-key' => [ 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ], |
2140
|
|
|
'nouploadmodule' => [ 'code' => 'nouploadmodule', 'info' => 'No upload module set' ], |
2141
|
|
|
'uploaddisabled' => [ |
2142
|
|
|
'code' => 'uploaddisabled', |
2143
|
|
|
'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' |
2144
|
|
|
], |
2145
|
|
|
'copyuploaddisabled' => [ |
2146
|
|
|
'code' => 'copyuploaddisabled', |
2147
|
|
|
'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' |
2148
|
|
|
], |
2149
|
|
|
'copyuploadbaddomain' => [ |
2150
|
|
|
'code' => 'copyuploadbaddomain', |
2151
|
|
|
'info' => 'Uploads by URL are not allowed from this domain.' |
2152
|
|
|
], |
2153
|
|
|
'copyuploadbadurl' => [ |
2154
|
|
|
'code' => 'copyuploadbadurl', |
2155
|
|
|
'info' => 'Upload not allowed from this URL.' |
2156
|
|
|
], |
2157
|
|
|
|
2158
|
|
|
'filename-tooshort' => [ |
2159
|
|
|
'code' => 'filename-tooshort', |
2160
|
|
|
'info' => 'The filename is too short' |
2161
|
|
|
], |
2162
|
|
|
'filename-toolong' => [ 'code' => 'filename-toolong', 'info' => 'The filename is too long' ], |
2163
|
|
|
'illegal-filename' => [ |
2164
|
|
|
'code' => 'illegal-filename', |
2165
|
|
|
'info' => 'The filename is not allowed' |
2166
|
|
|
], |
2167
|
|
|
'filetype-missing' => [ |
2168
|
|
|
'code' => 'filetype-missing', |
2169
|
|
|
'info' => 'The file is missing an extension' |
2170
|
|
|
], |
2171
|
|
|
|
2172
|
|
|
'mustbeloggedin' => [ 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' ] |
2173
|
|
|
]; |
2174
|
|
|
// @codingStandardsIgnoreEnd |
2175
|
|
|
|
2176
|
|
|
/** |
2177
|
|
|
* Helper function for readonly errors |
2178
|
|
|
* |
2179
|
|
|
* @throws UsageException always |
2180
|
|
|
*/ |
2181
|
|
|
public function dieReadOnly() { |
2182
|
|
|
$parsed = $this->parseMsg( [ 'readonlytext' ] ); |
2183
|
|
|
$this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0, |
2184
|
|
|
[ 'readonlyreason' => wfReadOnlyReason() ] ); |
2185
|
|
|
} |
2186
|
|
|
|
2187
|
|
|
/** |
2188
|
|
|
* Output the error message related to a certain array |
2189
|
|
|
* @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array |
2190
|
|
|
* @throws UsageException always |
2191
|
|
|
*/ |
2192
|
|
|
public function dieUsageMsg( $error ) { |
2193
|
|
|
# most of the time we send a 1 element, so we might as well send it as |
2194
|
|
|
# a string and make this an array here. |
2195
|
|
|
if ( is_string( $error ) ) { |
2196
|
|
|
$error = [ $error ]; |
2197
|
|
|
} |
2198
|
|
|
$parsed = $this->parseMsg( $error ); |
2199
|
|
|
$extraData = isset( $parsed['data'] ) ? $parsed['data'] : null; |
2200
|
|
|
$this->dieUsage( $parsed['info'], $parsed['code'], 0, $extraData ); |
2201
|
|
|
} |
2202
|
|
|
|
2203
|
|
|
/** |
2204
|
|
|
* Will only set a warning instead of failing if the global $wgDebugAPI |
2205
|
|
|
* is set to true. Otherwise behaves exactly as dieUsageMsg(). |
2206
|
|
|
* @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array |
2207
|
|
|
* @throws UsageException |
2208
|
|
|
* @since 1.21 |
2209
|
|
|
*/ |
2210
|
|
|
public function dieUsageMsgOrDebug( $error ) { |
2211
|
|
|
if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) { |
2212
|
|
|
$this->dieUsageMsg( $error ); |
2213
|
|
|
} |
2214
|
|
|
|
2215
|
|
|
if ( is_string( $error ) ) { |
2216
|
|
|
$error = [ $error ]; |
2217
|
|
|
} |
2218
|
|
|
$parsed = $this->parseMsg( $error ); |
2219
|
|
|
$this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] ); |
2220
|
|
|
} |
2221
|
|
|
|
2222
|
|
|
/** |
2223
|
|
|
* Die with the $prefix.'badcontinue' error. This call is common enough to |
2224
|
|
|
* make it into the base method. |
2225
|
|
|
* @param bool $condition Will only die if this value is true |
2226
|
|
|
* @throws UsageException |
2227
|
|
|
* @since 1.21 |
2228
|
|
|
*/ |
2229
|
|
|
protected function dieContinueUsageIf( $condition ) { |
2230
|
|
|
if ( $condition ) { |
2231
|
|
|
$this->dieUsage( |
2232
|
|
|
'Invalid continue param. You should pass the original value returned by the previous query', |
2233
|
|
|
'badcontinue' ); |
2234
|
|
|
} |
2235
|
|
|
} |
2236
|
|
|
|
2237
|
|
|
/** |
2238
|
|
|
* Return the error message related to a certain array |
2239
|
|
|
* @param array|string|MessageSpecifier $error Element of a getUserPermissionsErrors()-style array |
2240
|
|
|
* @return [ 'code' => code, 'info' => info ] |
|
|
|
|
2241
|
|
|
*/ |
2242
|
|
|
public function parseMsg( $error ) { |
2243
|
|
|
// Check whether someone passed the whole array, instead of one element as |
2244
|
|
|
// documented. This breaks if it's actually an array of fallback keys, but |
2245
|
|
|
// that's long-standing misbehavior introduced in r87627 to incorrectly |
2246
|
|
|
// fix T30797. |
2247
|
|
|
if ( is_array( $error ) ) { |
2248
|
|
|
$first = reset( $error ); |
2249
|
|
|
if ( is_array( $first ) ) { |
2250
|
|
|
wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) ); |
2251
|
|
|
$error = $first; |
2252
|
|
|
} |
2253
|
|
|
} |
2254
|
|
|
|
2255
|
|
|
$msg = Message::newFromSpecifier( $error ); |
2256
|
|
|
|
2257
|
|
|
if ( $msg instanceof IApiMessage ) { |
2258
|
|
|
return [ |
2259
|
|
|
'code' => $msg->getApiCode(), |
2260
|
|
|
'info' => $msg->inLanguage( 'en' )->useDatabase( false )->text(), |
2261
|
|
|
'data' => $msg->getApiData() |
2262
|
|
|
]; |
2263
|
|
|
} |
2264
|
|
|
|
2265
|
|
|
$key = $msg->getKey(); |
2266
|
|
|
if ( isset( self::$messageMap[$key] ) ) { |
2267
|
|
|
$params = $msg->getParams(); |
2268
|
|
|
return [ |
2269
|
|
|
'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $params ), |
2270
|
|
|
'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $params ) |
2271
|
|
|
]; |
2272
|
|
|
} |
2273
|
|
|
|
2274
|
|
|
// If the key isn't present, throw an "unknown error" |
2275
|
|
|
return $this->parseMsg( [ 'unknownerror', $key ] ); |
2276
|
|
|
} |
2277
|
|
|
|
2278
|
|
|
/** |
2279
|
|
|
* Internal code errors should be reported with this method |
2280
|
|
|
* @param string $method Method or function name |
2281
|
|
|
* @param string $message Error message |
2282
|
|
|
* @throws MWException always |
2283
|
|
|
*/ |
2284
|
|
|
protected static function dieDebug( $method, $message ) { |
2285
|
|
|
throw new MWException( "Internal error in $method: $message" ); |
2286
|
|
|
} |
2287
|
|
|
|
2288
|
|
|
/** |
2289
|
|
|
* Write logging information for API features to a debug log, for usage |
2290
|
|
|
* analysis. |
2291
|
|
|
* @param string $feature Feature being used. |
2292
|
|
|
*/ |
2293
|
|
|
public function logFeatureUsage( $feature ) { |
2294
|
|
|
$request = $this->getRequest(); |
2295
|
|
|
$s = '"' . addslashes( $feature ) . '"' . |
2296
|
|
|
' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' . |
2297
|
|
|
' "' . $request->getIP() . '"' . |
2298
|
|
|
' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' . |
2299
|
|
|
' "' . addslashes( $this->getMain()->getUserAgent() ) . '"'; |
2300
|
|
|
wfDebugLog( 'api-feature-usage', $s, 'private' ); |
2301
|
|
|
} |
2302
|
|
|
|
2303
|
|
|
/**@}*/ |
2304
|
|
|
|
2305
|
|
|
/************************************************************************//** |
2306
|
|
|
* @name Help message generation |
2307
|
|
|
* @{ |
2308
|
|
|
*/ |
2309
|
|
|
|
2310
|
|
|
/** |
2311
|
|
|
* Return the description message. |
2312
|
|
|
* |
2313
|
|
|
* @return string|array|Message |
2314
|
|
|
*/ |
2315
|
|
|
protected function getDescriptionMessage() { |
2316
|
|
|
return "apihelp-{$this->getModulePath()}-description"; |
2317
|
|
|
} |
2318
|
|
|
|
2319
|
|
|
/** |
2320
|
|
|
* Get final module description, after hooks have had a chance to tweak it as |
2321
|
|
|
* needed. |
2322
|
|
|
* |
2323
|
|
|
* @since 1.25, returns Message[] rather than string[] |
2324
|
|
|
* @return Message[] |
2325
|
|
|
*/ |
2326
|
|
|
public function getFinalDescription() { |
2327
|
|
|
$desc = $this->getDescription(); |
2328
|
|
|
Hooks::run( 'APIGetDescription', [ &$this, &$desc ] ); |
2329
|
|
|
$desc = self::escapeWikiText( $desc ); |
2330
|
|
|
if ( is_array( $desc ) ) { |
2331
|
|
|
$desc = implode( "\n", $desc ); |
2332
|
|
|
} else { |
2333
|
|
|
$desc = (string)$desc; |
2334
|
|
|
} |
2335
|
|
|
|
2336
|
|
|
$msg = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [ |
2337
|
|
|
$this->getModulePrefix(), |
2338
|
|
|
$this->getModuleName(), |
2339
|
|
|
$this->getModulePath(), |
2340
|
|
|
] ); |
2341
|
|
|
if ( !$msg->exists() ) { |
2342
|
|
|
$msg = $this->msg( 'api-help-fallback-description', $desc ); |
2343
|
|
|
} |
2344
|
|
|
$msgs = [ $msg ]; |
2345
|
|
|
|
2346
|
|
|
Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] ); |
2347
|
|
|
|
2348
|
|
|
return $msgs; |
2349
|
|
|
} |
2350
|
|
|
|
2351
|
|
|
/** |
2352
|
|
|
* Get final list of parameters, after hooks have had a chance to |
2353
|
|
|
* tweak it as needed. |
2354
|
|
|
* |
2355
|
|
|
* @param int $flags Zero or more flags like GET_VALUES_FOR_HELP |
2356
|
|
|
* @return array|bool False on no parameters |
2357
|
|
|
* @since 1.21 $flags param added |
2358
|
|
|
*/ |
2359
|
|
|
public function getFinalParams( $flags = 0 ) { |
2360
|
|
|
$params = $this->getAllowedParams( $flags ); |
2361
|
|
|
if ( !$params ) { |
2362
|
|
|
$params = []; |
2363
|
|
|
} |
2364
|
|
|
|
2365
|
|
|
if ( $this->needsToken() ) { |
|
|
|
|
2366
|
|
|
$params['token'] = [ |
2367
|
|
|
ApiBase::PARAM_TYPE => 'string', |
2368
|
|
|
ApiBase::PARAM_REQUIRED => true, |
2369
|
|
|
ApiBase::PARAM_HELP_MSG => [ |
2370
|
|
|
'api-help-param-token', |
2371
|
|
|
$this->needsToken(), |
2372
|
|
|
], |
2373
|
|
|
] + ( isset( $params['token'] ) ? $params['token'] : [] ); |
2374
|
|
|
} |
2375
|
|
|
|
2376
|
|
|
Hooks::run( 'APIGetAllowedParams', [ &$this, &$params, $flags ] ); |
2377
|
|
|
|
2378
|
|
|
return $params; |
2379
|
|
|
} |
2380
|
|
|
|
2381
|
|
|
/** |
2382
|
|
|
* Get final parameter descriptions, after hooks have had a chance to tweak it as |
2383
|
|
|
* needed. |
2384
|
|
|
* |
2385
|
|
|
* @since 1.25, returns array of Message[] rather than array of string[] |
2386
|
|
|
* @return array Keys are parameter names, values are arrays of Message objects |
2387
|
|
|
*/ |
2388
|
|
|
public function getFinalParamDescription() { |
2389
|
|
|
$prefix = $this->getModulePrefix(); |
2390
|
|
|
$name = $this->getModuleName(); |
2391
|
|
|
$path = $this->getModulePath(); |
2392
|
|
|
|
2393
|
|
|
$desc = $this->getParamDescription(); |
2394
|
|
|
Hooks::run( 'APIGetParamDescription', [ &$this, &$desc ] ); |
2395
|
|
|
|
2396
|
|
|
if ( !$desc ) { |
2397
|
|
|
$desc = []; |
2398
|
|
|
} |
2399
|
|
|
$desc = self::escapeWikiText( $desc ); |
|
|
|
|
2400
|
|
|
|
2401
|
|
|
$params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP ); |
2402
|
|
|
$msgs = []; |
2403
|
|
|
foreach ( $params as $param => $settings ) { |
|
|
|
|
2404
|
|
|
if ( !is_array( $settings ) ) { |
2405
|
|
|
$settings = []; |
2406
|
|
|
} |
2407
|
|
|
|
2408
|
|
|
$d = isset( $desc[$param] ) ? $desc[$param] : ''; |
2409
|
|
|
if ( is_array( $d ) ) { |
2410
|
|
|
// Special handling for prop parameters |
2411
|
|
|
$d = array_map( function ( $line ) { |
2412
|
|
|
if ( preg_match( '/^\s+(\S+)\s+-\s+(.+)$/', $line, $m ) ) { |
2413
|
|
|
$line = "\n;{$m[1]}:{$m[2]}"; |
2414
|
|
|
} |
2415
|
|
|
return $line; |
2416
|
|
|
}, $d ); |
2417
|
|
|
$d = implode( ' ', $d ); |
2418
|
|
|
} |
2419
|
|
|
|
2420
|
|
|
if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) { |
2421
|
|
|
$msg = $settings[ApiBase::PARAM_HELP_MSG]; |
2422
|
|
|
} else { |
2423
|
|
|
$msg = $this->msg( "apihelp-{$path}-param-{$param}" ); |
2424
|
|
|
if ( !$msg->exists() ) { |
2425
|
|
|
$msg = $this->msg( 'api-help-fallback-parameter', $d ); |
2426
|
|
|
} |
2427
|
|
|
} |
2428
|
|
|
$msg = ApiBase::makeMessage( $msg, $this->getContext(), |
2429
|
|
|
[ $prefix, $param, $name, $path ] ); |
2430
|
|
|
if ( !$msg ) { |
2431
|
|
|
self::dieDebug( __METHOD__, |
2432
|
|
|
'Value in ApiBase::PARAM_HELP_MSG is not valid' ); |
2433
|
|
|
} |
2434
|
|
|
$msgs[$param] = [ $msg ]; |
2435
|
|
|
|
2436
|
|
|
if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) { |
2437
|
|
|
if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) { |
2438
|
|
|
self::dieDebug( __METHOD__, |
2439
|
|
|
'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' ); |
2440
|
|
|
} |
2441
|
|
|
if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) { |
2442
|
|
|
self::dieDebug( __METHOD__, |
2443
|
|
|
'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' . |
2444
|
|
|
'ApiBase::PARAM_TYPE is an array' ); |
2445
|
|
|
} |
2446
|
|
|
|
2447
|
|
|
$valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE]; |
2448
|
|
|
foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) { |
2449
|
|
|
if ( isset( $valueMsgs[$value] ) ) { |
2450
|
|
|
$msg = $valueMsgs[$value]; |
2451
|
|
|
} else { |
2452
|
|
|
$msg = "apihelp-{$path}-paramvalue-{$param}-{$value}"; |
2453
|
|
|
} |
2454
|
|
|
$m = ApiBase::makeMessage( $msg, $this->getContext(), |
2455
|
|
|
[ $prefix, $param, $name, $path, $value ] ); |
2456
|
|
|
if ( $m ) { |
2457
|
|
|
$m = new ApiHelpParamValueMessage( |
2458
|
|
|
$value, |
2459
|
|
|
[ $m->getKey(), 'api-help-param-no-description' ], |
2460
|
|
|
$m->getParams() |
2461
|
|
|
); |
2462
|
|
|
$msgs[$param][] = $m->setContext( $this->getContext() ); |
2463
|
|
|
} else { |
2464
|
|
|
self::dieDebug( __METHOD__, |
2465
|
|
|
"Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" ); |
2466
|
|
|
} |
2467
|
|
|
} |
2468
|
|
|
} |
2469
|
|
|
|
2470
|
|
|
if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) { |
2471
|
|
|
if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) { |
2472
|
|
|
self::dieDebug( __METHOD__, |
2473
|
|
|
'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' ); |
2474
|
|
|
} |
2475
|
|
|
foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) { |
2476
|
|
|
$m = ApiBase::makeMessage( $m, $this->getContext(), |
2477
|
|
|
[ $prefix, $param, $name, $path ] ); |
2478
|
|
|
if ( $m ) { |
2479
|
|
|
$msgs[$param][] = $m; |
2480
|
|
|
} else { |
2481
|
|
|
self::dieDebug( __METHOD__, |
2482
|
|
|
'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' ); |
2483
|
|
|
} |
2484
|
|
|
} |
2485
|
|
|
} |
2486
|
|
|
} |
2487
|
|
|
|
2488
|
|
|
Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] ); |
2489
|
|
|
|
2490
|
|
|
return $msgs; |
2491
|
|
|
} |
2492
|
|
|
|
2493
|
|
|
/** |
2494
|
|
|
* Generates the list of flags for the help screen and for action=paraminfo |
2495
|
|
|
* |
2496
|
|
|
* Corresponding messages: api-help-flag-deprecated, |
2497
|
|
|
* api-help-flag-internal, api-help-flag-readrights, |
2498
|
|
|
* api-help-flag-writerights, api-help-flag-mustbeposted |
2499
|
|
|
* |
2500
|
|
|
* @return string[] |
2501
|
|
|
*/ |
2502
|
|
|
protected function getHelpFlags() { |
2503
|
|
|
$flags = []; |
2504
|
|
|
|
2505
|
|
|
if ( $this->isDeprecated() ) { |
2506
|
|
|
$flags[] = 'deprecated'; |
2507
|
|
|
} |
2508
|
|
|
if ( $this->isInternal() ) { |
2509
|
|
|
$flags[] = 'internal'; |
2510
|
|
|
} |
2511
|
|
|
if ( $this->isReadMode() ) { |
2512
|
|
|
$flags[] = 'readrights'; |
2513
|
|
|
} |
2514
|
|
|
if ( $this->isWriteMode() ) { |
2515
|
|
|
$flags[] = 'writerights'; |
2516
|
|
|
} |
2517
|
|
|
if ( $this->mustBePosted() ) { |
2518
|
|
|
$flags[] = 'mustbeposted'; |
2519
|
|
|
} |
2520
|
|
|
|
2521
|
|
|
return $flags; |
2522
|
|
|
} |
2523
|
|
|
|
2524
|
|
|
/** |
2525
|
|
|
* Returns information about the source of this module, if known |
2526
|
|
|
* |
2527
|
|
|
* Returned array is an array with the following keys: |
2528
|
|
|
* - path: Install path |
2529
|
|
|
* - name: Extension name, or "MediaWiki" for core |
2530
|
|
|
* - namemsg: (optional) i18n message key for a display name |
2531
|
|
|
* - license-name: (optional) Name of license |
2532
|
|
|
* |
2533
|
|
|
* @return array|null |
2534
|
|
|
*/ |
2535
|
|
|
protected function getModuleSourceInfo() { |
2536
|
|
|
global $IP; |
2537
|
|
|
|
2538
|
|
|
if ( $this->mModuleSource !== false ) { |
2539
|
|
|
return $this->mModuleSource; |
2540
|
|
|
} |
2541
|
|
|
|
2542
|
|
|
// First, try to find where the module comes from... |
2543
|
|
|
$rClass = new ReflectionClass( $this ); |
2544
|
|
|
$path = $rClass->getFileName(); |
2545
|
|
|
if ( !$path ) { |
2546
|
|
|
// No path known? |
2547
|
|
|
$this->mModuleSource = null; |
2548
|
|
|
return null; |
2549
|
|
|
} |
2550
|
|
|
$path = realpath( $path ) ?: $path; |
2551
|
|
|
|
2552
|
|
|
// Build map of extension directories to extension info |
2553
|
|
|
if ( self::$extensionInfo === null ) { |
2554
|
|
|
$extDir = $this->getConfig()->get( 'ExtensionDirectory' ); |
2555
|
|
|
self::$extensionInfo = [ |
2556
|
|
|
realpath( __DIR__ ) ?: __DIR__ => [ |
2557
|
|
|
'path' => $IP, |
2558
|
|
|
'name' => 'MediaWiki', |
2559
|
|
|
'license-name' => 'GPL-2.0+', |
2560
|
|
|
], |
2561
|
|
|
realpath( "$IP/extensions" ) ?: "$IP/extensions" => null, |
2562
|
|
|
realpath( $extDir ) ?: $extDir => null, |
2563
|
|
|
]; |
2564
|
|
|
$keep = [ |
2565
|
|
|
'path' => null, |
2566
|
|
|
'name' => null, |
2567
|
|
|
'namemsg' => null, |
2568
|
|
|
'license-name' => null, |
2569
|
|
|
]; |
2570
|
|
|
foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) { |
2571
|
|
|
foreach ( $group as $ext ) { |
2572
|
|
|
if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) { |
2573
|
|
|
// This shouldn't happen, but does anyway. |
2574
|
|
|
continue; |
2575
|
|
|
} |
2576
|
|
|
|
2577
|
|
|
$extpath = $ext['path']; |
2578
|
|
|
if ( !is_dir( $extpath ) ) { |
2579
|
|
|
$extpath = dirname( $extpath ); |
2580
|
|
|
} |
2581
|
|
|
self::$extensionInfo[realpath( $extpath ) ?: $extpath] = |
2582
|
|
|
array_intersect_key( $ext, $keep ); |
2583
|
|
|
} |
2584
|
|
|
} |
2585
|
|
|
foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) { |
2586
|
|
|
$extpath = $ext['path']; |
2587
|
|
|
if ( !is_dir( $extpath ) ) { |
2588
|
|
|
$extpath = dirname( $extpath ); |
2589
|
|
|
} |
2590
|
|
|
self::$extensionInfo[realpath( $extpath ) ?: $extpath] = |
2591
|
|
|
array_intersect_key( $ext, $keep ); |
2592
|
|
|
} |
2593
|
|
|
} |
2594
|
|
|
|
2595
|
|
|
// Now traverse parent directories until we find a match or run out of |
2596
|
|
|
// parents. |
2597
|
|
|
do { |
2598
|
|
|
if ( array_key_exists( $path, self::$extensionInfo ) ) { |
2599
|
|
|
// Found it! |
2600
|
|
|
$this->mModuleSource = self::$extensionInfo[$path]; |
2601
|
|
|
return $this->mModuleSource; |
2602
|
|
|
} |
2603
|
|
|
|
2604
|
|
|
$oldpath = $path; |
2605
|
|
|
$path = dirname( $path ); |
2606
|
|
|
} while ( $path !== $oldpath ); |
2607
|
|
|
|
2608
|
|
|
// No idea what extension this might be. |
2609
|
|
|
$this->mModuleSource = null; |
2610
|
|
|
return null; |
2611
|
|
|
} |
2612
|
|
|
|
2613
|
|
|
/** |
2614
|
|
|
* Called from ApiHelp before the pieces are joined together and returned. |
2615
|
|
|
* |
2616
|
|
|
* This exists mainly for ApiMain to add the Permissions and Credits |
2617
|
|
|
* sections. Other modules probably don't need it. |
2618
|
|
|
* |
2619
|
|
|
* @param string[] &$help Array of help data |
2620
|
|
|
* @param array $options Options passed to ApiHelp::getHelp |
2621
|
|
|
* @param array &$tocData If a TOC is being generated, this array has keys |
2622
|
|
|
* as anchors in the page and values as for Linker::generateTOC(). |
2623
|
|
|
*/ |
2624
|
|
|
public function modifyHelp( array &$help, array $options, array &$tocData ) { |
2625
|
|
|
} |
2626
|
|
|
|
2627
|
|
|
/**@}*/ |
2628
|
|
|
|
2629
|
|
|
/************************************************************************//** |
2630
|
|
|
* @name Deprecated |
2631
|
|
|
* @{ |
2632
|
|
|
*/ |
2633
|
|
|
|
2634
|
|
|
/** |
2635
|
|
|
* Returns the description string for this module |
2636
|
|
|
* |
2637
|
|
|
* Ignored if an i18n message exists for |
2638
|
|
|
* "apihelp-{$this->getModulePath()}-description". |
2639
|
|
|
* |
2640
|
|
|
* @deprecated since 1.25 |
2641
|
|
|
* @return Message|string|array |
2642
|
|
|
*/ |
2643
|
|
|
protected function getDescription() { |
2644
|
|
|
return false; |
2645
|
|
|
} |
2646
|
|
|
|
2647
|
|
|
/** |
2648
|
|
|
* Returns an array of parameter descriptions. |
2649
|
|
|
* |
2650
|
|
|
* For each parameter, ignored if an i18n message exists for the parameter. |
2651
|
|
|
* By default that message is |
2652
|
|
|
* "apihelp-{$this->getModulePath()}-param-{$param}", but it may be |
2653
|
|
|
* overridden using ApiBase::PARAM_HELP_MSG in the data returned by |
2654
|
|
|
* self::getFinalParams(). |
2655
|
|
|
* |
2656
|
|
|
* @deprecated since 1.25 |
2657
|
|
|
* @return array|bool False on no parameter descriptions |
2658
|
|
|
*/ |
2659
|
|
|
protected function getParamDescription() { |
2660
|
|
|
return []; |
2661
|
|
|
} |
2662
|
|
|
|
2663
|
|
|
/** |
2664
|
|
|
* Returns usage examples for this module. |
2665
|
|
|
* |
2666
|
|
|
* Return value as an array is either: |
2667
|
|
|
* - numeric keys with partial URLs ("api.php?" plus a query string) as |
2668
|
|
|
* values |
2669
|
|
|
* - sequential numeric keys with even-numbered keys being display-text |
2670
|
|
|
* and odd-numbered keys being partial urls |
2671
|
|
|
* - partial URLs as keys with display-text (string or array-to-be-joined) |
2672
|
|
|
* as values |
2673
|
|
|
* Return value as a string is the same as an array with a numeric key and |
2674
|
|
|
* that value, and boolean false means "no examples". |
2675
|
|
|
* |
2676
|
|
|
* @deprecated since 1.25, use getExamplesMessages() instead |
2677
|
|
|
* @return bool|string|array |
2678
|
|
|
*/ |
2679
|
|
|
protected function getExamples() { |
2680
|
|
|
return false; |
2681
|
|
|
} |
2682
|
|
|
|
2683
|
|
|
/** |
2684
|
|
|
* @deprecated since 1.25, always returns empty string |
2685
|
|
|
* @param IDatabase|bool $db |
2686
|
|
|
* @return string |
2687
|
|
|
*/ |
2688
|
|
|
public function getModuleProfileName( $db = false ) { |
2689
|
|
|
wfDeprecated( __METHOD__, '1.25' ); |
2690
|
|
|
return ''; |
2691
|
|
|
} |
2692
|
|
|
|
2693
|
|
|
/** |
2694
|
|
|
* @deprecated since 1.25 |
2695
|
|
|
*/ |
2696
|
|
|
public function profileIn() { |
2697
|
|
|
// No wfDeprecated() yet because extensions call this and might need to |
2698
|
|
|
// keep doing so for BC. |
2699
|
|
|
} |
2700
|
|
|
|
2701
|
|
|
/** |
2702
|
|
|
* @deprecated since 1.25 |
2703
|
|
|
*/ |
2704
|
|
|
public function profileOut() { |
2705
|
|
|
// No wfDeprecated() yet because extensions call this and might need to |
2706
|
|
|
// keep doing so for BC. |
2707
|
|
|
} |
2708
|
|
|
|
2709
|
|
|
/** |
2710
|
|
|
* @deprecated since 1.25 |
2711
|
|
|
*/ |
2712
|
|
|
public function safeProfileOut() { |
2713
|
|
|
wfDeprecated( __METHOD__, '1.25' ); |
2714
|
|
|
} |
2715
|
|
|
|
2716
|
|
|
/** |
2717
|
|
|
* @deprecated since 1.25, always returns 0 |
2718
|
|
|
* @return float |
2719
|
|
|
*/ |
2720
|
|
|
public function getProfileTime() { |
2721
|
|
|
wfDeprecated( __METHOD__, '1.25' ); |
2722
|
|
|
return 0; |
2723
|
|
|
} |
2724
|
|
|
|
2725
|
|
|
/** |
2726
|
|
|
* @deprecated since 1.25 |
2727
|
|
|
*/ |
2728
|
|
|
public function profileDBIn() { |
2729
|
|
|
wfDeprecated( __METHOD__, '1.25' ); |
2730
|
|
|
} |
2731
|
|
|
|
2732
|
|
|
/** |
2733
|
|
|
* @deprecated since 1.25 |
2734
|
|
|
*/ |
2735
|
|
|
public function profileDBOut() { |
2736
|
|
|
wfDeprecated( __METHOD__, '1.25' ); |
2737
|
|
|
} |
2738
|
|
|
|
2739
|
|
|
/** |
2740
|
|
|
* @deprecated since 1.25, always returns 0 |
2741
|
|
|
* @return float |
2742
|
|
|
*/ |
2743
|
|
|
public function getProfileDBTime() { |
2744
|
|
|
wfDeprecated( __METHOD__, '1.25' ); |
2745
|
|
|
return 0; |
2746
|
|
|
} |
2747
|
|
|
|
2748
|
|
|
/** |
2749
|
|
|
* Call wfTransactionalTimeLimit() if this request was POSTed |
2750
|
|
|
* @since 1.26 |
2751
|
|
|
*/ |
2752
|
|
|
protected function useTransactionalTimeLimit() { |
2753
|
|
|
if ( $this->getRequest()->wasPosted() ) { |
2754
|
|
|
wfTransactionalTimeLimit(); |
2755
|
|
|
} |
2756
|
|
|
} |
2757
|
|
|
|
2758
|
|
|
/**@}*/ |
2759
|
|
|
} |
2760
|
|
|
|
2761
|
|
|
/** |
2762
|
|
|
* For really cool vim folding this needs to be at the end: |
2763
|
|
|
* vim: foldmarker=@{,@} foldmethod=marker |
2764
|
|
|
*/ |
2765
|
|
|
|
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.