Merge "ResourceLoader: Deprecate ResourceLoader::makeConfigSetScript"
[mediawiki.git] / includes / api / ApiParamInfo.php
blob2d43f23722ad96d8bb1faea80bdf5252d2801f79
1 <?php
2 /**
3 * Copyright © 2008 Roan Kattouw <roan.kattouw@gmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
23 namespace MediaWiki\Api;
25 use MediaWiki\Context\RequestContext;
26 use MediaWiki\Message\Message;
27 use MediaWiki\Parser\Parser;
28 use MediaWiki\SpecialPage\SpecialPage;
29 use MediaWiki\User\UserFactory;
30 use MediaWiki\Utils\ExtensionInfo;
31 use Wikimedia\ParamValidator\ParamValidator;
33 /**
34 * @ingroup API
36 class ApiParamInfo extends ApiBase {
38 /** @var string */
39 private $helpFormat;
41 /** @var RequestContext */
42 private $context;
44 /** @var UserFactory */
45 private $userFactory;
47 public function __construct(
48 ApiMain $main,
49 string $action,
50 UserFactory $userFactory
51 ) {
52 parent::__construct( $main, $action );
53 $this->userFactory = $userFactory;
56 public function execute() {
57 // Get parameters
58 $params = $this->extractRequestParams();
60 $this->helpFormat = $params['helpformat'];
61 $this->context = new RequestContext;
62 $this->context->setUser( $this->userFactory->newAnonymous() ); // anon to avoid caching issues
63 $this->context->setLanguage( $this->getMain()->getLanguage() );
65 if ( is_array( $params['modules'] ) ) {
66 $modules = [];
67 foreach ( $params['modules'] as $path ) {
68 if ( $path === '*' || $path === '**' ) {
69 $path = "main+$path";
71 if ( str_ends_with( $path, '+*' ) || str_ends_with( $path, ' *' ) ) {
72 $submodules = true;
73 $path = substr( $path, 0, -2 );
74 $recursive = false;
75 } elseif ( str_ends_with( $path, '+**' ) || str_ends_with( $path, ' **' ) ) {
76 $submodules = true;
77 $path = substr( $path, 0, -3 );
78 $recursive = true;
79 } else {
80 $submodules = false;
83 if ( $submodules ) {
84 try {
85 $module = $this->getModuleFromPath( $path );
86 } catch ( ApiUsageException $ex ) {
87 foreach ( $ex->getStatusValue()->getMessages() as $error ) {
88 $this->addWarning( $error );
90 continue;
92 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
93 // recursive is set when used
94 $submodules = $this->listAllSubmodules( $module, $recursive );
95 if ( $submodules ) {
96 $modules = array_merge( $modules, $submodules );
97 } else {
98 $this->addWarning( [ 'apierror-badmodule-nosubmodules', $path ], 'badmodule' );
100 } else {
101 $modules[] = $path;
104 } else {
105 $modules = [];
108 if ( is_array( $params['querymodules'] ) ) {
109 $queryModules = $params['querymodules'];
110 foreach ( $queryModules as $m ) {
111 $modules[] = 'query+' . $m;
113 } else {
114 $queryModules = [];
117 if ( is_array( $params['formatmodules'] ) ) {
118 $formatModules = $params['formatmodules'];
119 foreach ( $formatModules as $m ) {
120 $modules[] = $m;
122 } else {
123 $formatModules = [];
126 $modules = array_unique( $modules );
128 $res = [];
130 foreach ( $modules as $m ) {
131 try {
132 $module = $this->getModuleFromPath( $m );
133 } catch ( ApiUsageException $ex ) {
134 foreach ( $ex->getStatusValue()->getMessages() as $error ) {
135 $this->addWarning( $error );
137 continue;
139 $key = 'modules';
141 // Back compat
142 $isBCQuery = false;
143 if ( $module->getParent() && $module->getParent()->getModuleName() == 'query' &&
144 in_array( $module->getModuleName(), $queryModules )
146 $isBCQuery = true;
147 $key = 'querymodules';
149 if ( in_array( $module->getModuleName(), $formatModules ) ) {
150 $key = 'formatmodules';
153 $item = $this->getModuleInfo( $module );
154 if ( $isBCQuery ) {
155 $item['querytype'] = $item['group'];
157 $res[$key][] = $item;
160 $result = $this->getResult();
161 $result->addValue( [ $this->getModuleName() ], 'helpformat', $this->helpFormat );
163 foreach ( $res as $key => $stuff ) {
164 ApiResult::setIndexedTagName( $res[$key], 'module' );
167 if ( $params['mainmodule'] ) {
168 $res['mainmodule'] = $this->getModuleInfo( $this->getMain() );
171 if ( $params['pagesetmodule'] ) {
172 $pageSet = new ApiPageSet( $this->getMain()->getModuleManager()->getModule( 'query' ) );
173 $res['pagesetmodule'] = $this->getModuleInfo( $pageSet );
174 unset( $res['pagesetmodule']['name'] );
175 unset( $res['pagesetmodule']['path'] );
176 unset( $res['pagesetmodule']['group'] );
179 $result->addValue( null, $this->getModuleName(), $res );
183 * List all submodules of a module
184 * @param ApiBase $module
185 * @param bool $recursive
186 * @return string[]
188 private function listAllSubmodules( ApiBase $module, $recursive ) {
189 $paths = [];
190 $manager = $module->getModuleManager();
191 if ( $manager ) {
192 $names = $manager->getNames();
193 sort( $names );
194 foreach ( $names as $name ) {
195 $submodule = $manager->getModule( $name );
196 $paths[] = $submodule->getModulePath();
197 if ( $recursive && $submodule->getModuleManager() ) {
198 $paths = array_merge( $paths, $this->listAllSubmodules( $submodule, $recursive ) );
202 return $paths;
206 * @param array &$res Result array
207 * @param string $key Result key
208 * @param Message[] $msgs
209 * @param bool $joinLists
211 protected function formatHelpMessages( array &$res, $key, array $msgs, $joinLists = false ) {
212 switch ( $this->helpFormat ) {
213 case 'none':
214 break;
216 case 'wikitext':
217 $ret = [];
218 foreach ( $msgs as $m ) {
219 $ret[] = $m->setContext( $this->context )->text();
221 $res[$key] = implode( "\n\n", $ret );
222 if ( $joinLists ) {
223 $res[$key] = preg_replace( '!^(([*#:;])[^\n]*)\n\n(?=\2)!m', "$1\n", $res[$key] );
225 break;
227 case 'html':
228 $ret = [];
229 foreach ( $msgs as $m ) {
230 $ret[] = $m->setContext( $this->context )->parseAsBlock();
232 $ret = implode( "\n", $ret );
233 if ( $joinLists ) {
234 $ret = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $ret );
236 $res[$key] = Parser::stripOuterParagraph( $ret );
237 break;
239 case 'raw':
240 $res[$key] = [];
241 foreach ( $msgs as $m ) {
242 $a = [
243 'key' => $m->getKey(),
244 'params' => $m->getParams(),
246 ApiResult::setIndexedTagName( $a['params'], 'param' );
247 if ( $m instanceof ApiHelpParamValueMessage ) {
248 $a['forvalue'] = $m->getParamValue();
250 $res[$key][] = $a;
252 ApiResult::setIndexedTagName( $res[$key], 'msg' );
253 break;
258 * @param ApiBase $module
259 * @return array
261 private function getModuleInfo( $module ) {
262 $ret = [];
263 $path = $module->getModulePath();
264 $paramValidator = $module->getMain()->getParamValidator();
266 $ret['name'] = $module->getModuleName();
267 $ret['classname'] = get_class( $module );
268 $ret['path'] = $path;
269 if ( !$module->isMain() ) {
270 $ret['group'] = $module->getParent()->getModuleManager()->getModuleGroup(
271 $module->getModuleName()
274 $ret['prefix'] = $module->getModulePrefix();
276 $sourceInfo = $module->getModuleSourceInfo();
277 if ( $sourceInfo ) {
278 $ret['source'] = $sourceInfo['name'];
279 if ( isset( $sourceInfo['namemsg'] ) ) {
280 $ret['sourcename'] = $this->context->msg( $sourceInfo['namemsg'] )->text();
281 } else {
282 $ret['sourcename'] = $ret['source'];
285 $link = SpecialPage::getTitleFor( 'Version', 'License/' . $sourceInfo['name'] )->getFullURL();
286 if ( isset( $sourceInfo['license-name'] ) ) {
287 $ret['licensetag'] = $sourceInfo['license-name'];
288 $ret['licenselink'] = (string)$link;
289 } elseif ( ExtensionInfo::getLicenseFileNames( dirname( $sourceInfo['path'] ) ) ) {
290 $ret['licenselink'] = (string)$link;
294 $this->formatHelpMessages( $ret, 'description', $module->getFinalDescription() );
296 foreach ( $module->getHelpFlags() as $flag ) {
297 $ret[$flag] = true;
300 $ret['helpurls'] = (array)$module->getHelpUrls();
301 if ( isset( $ret['helpurls'][0] ) && $ret['helpurls'][0] === false ) {
302 $ret['helpurls'] = [];
304 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
305 ApiResult::setIndexedTagName( $ret['helpurls'], 'helpurl' );
307 if ( $this->helpFormat !== 'none' ) {
308 $ret['examples'] = [];
309 $examples = $module->getExamplesMessages();
310 foreach ( $examples as $qs => $msg ) {
311 $item = [
312 'query' => $qs
314 $msg = $this->msg(
315 Message::newFromSpecifier( $msg ),
316 $module->getModulePrefix(),
317 $module->getModuleName(),
318 $module->getModulePath()
320 $this->formatHelpMessages( $item, 'description', [ $msg ] );
321 if ( isset( $item['description'] ) ) {
322 if ( is_array( $item['description'] ) ) {
323 $item['description'] = $item['description'][0];
324 } else {
325 ApiResult::setSubelementsList( $item, 'description' );
328 $ret['examples'][] = $item;
330 ApiResult::setIndexedTagName( $ret['examples'], 'example' );
333 $ret['parameters'] = [];
334 $ret['templatedparameters'] = [];
335 $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
336 $paramDesc = $module->getFinalParamDescription();
337 $index = 0;
338 foreach ( $params as $name => $settings ) {
339 $settings = $paramValidator->normalizeSettings( $settings );
341 $item = [
342 'index' => ++$index,
343 'name' => $name,
346 if ( !empty( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ) {
347 $item['templatevars'] = $settings[ApiBase::PARAM_TEMPLATE_VARS];
348 ApiResult::setIndexedTagName( $item['templatevars'], 'var' );
351 if ( isset( $paramDesc[$name] ) ) {
352 $this->formatHelpMessages( $item, 'description', $paramDesc[$name], true );
355 foreach ( $paramValidator->getParamInfo( $module, $name, $settings, [] ) as $k => $v ) {
356 $item[$k] = $v;
359 if ( $name === 'token' && $module->needsToken() ) {
360 $item['tokentype'] = $module->needsToken();
363 if ( $item['type'] === 'NULL' ) {
364 // Munge "NULL" to "string" for historical reasons
365 $item['type'] = 'string';
366 } elseif ( is_array( $item['type'] ) ) {
367 // Set indexed tag name, for historical reasons
368 ApiResult::setIndexedTagName( $item['type'], 't' );
371 if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
372 $item['info'] = [];
373 foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
374 $tag = array_shift( $i );
375 $info = [
376 'name' => $tag,
378 if ( count( $i ) ) {
379 $info['values'] = $i;
380 ApiResult::setIndexedTagName( $info['values'], 'v' );
382 $this->formatHelpMessages( $info, 'text', [
383 $this->context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
384 ->numParams( count( $i ) )
385 ->params( $this->context->getLanguage()->commaList( $i ) )
386 ->params( $module->getModulePrefix() )
387 ] );
388 ApiResult::setSubelementsList( $info, 'text' );
389 $item['info'][] = $info;
391 ApiResult::setIndexedTagName( $item['info'], 'i' );
394 $key = empty( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ? 'parameters' : 'templatedparameters';
395 $ret[$key][] = $item;
397 ApiResult::setIndexedTagName( $ret['parameters'], 'param' );
398 ApiResult::setIndexedTagName( $ret['templatedparameters'], 'param' );
400 $dynamicParams = $module->dynamicParameterDocumentation();
401 if ( $dynamicParams !== null ) {
402 if ( $this->helpFormat === 'none' ) {
403 $ret['dynamicparameters'] = true;
404 } else {
405 $dynamicParams = $this->msg(
406 Message::newFromSpecifier( $dynamicParams ),
407 $module->getModulePrefix(),
408 $module->getModuleName(),
409 $module->getModulePath()
411 $this->formatHelpMessages( $ret, 'dynamicparameters', [ $dynamicParams ] );
415 return $ret;
418 public function isReadMode() {
419 return false;
422 public function getAllowedParams() {
423 // back compat
424 $querymodules = $this->getMain()->getModuleManager()
425 ->getModule( 'query' )->getModuleManager()->getNames();
426 sort( $querymodules );
427 $formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
428 sort( $formatmodules );
430 return [
431 'modules' => [
432 ParamValidator::PARAM_ISMULTI => true,
434 'helpformat' => [
435 ParamValidator::PARAM_DEFAULT => 'none',
436 ParamValidator::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
439 'querymodules' => [
440 ParamValidator::PARAM_DEPRECATED => true,
441 ParamValidator::PARAM_ISMULTI => true,
442 ParamValidator::PARAM_TYPE => $querymodules,
444 'mainmodule' => [
445 ParamValidator::PARAM_DEPRECATED => true,
447 'pagesetmodule' => [
448 ParamValidator::PARAM_DEPRECATED => true,
450 'formatmodules' => [
451 ParamValidator::PARAM_DEPRECATED => true,
452 ParamValidator::PARAM_ISMULTI => true,
453 ParamValidator::PARAM_TYPE => $formatmodules,
458 protected function getExamplesMessages() {
459 return [
460 'action=paraminfo&modules=parse|phpfm|query%2Ballpages|query%2Bsiteinfo'
461 => 'apihelp-paraminfo-example-1',
462 'action=paraminfo&modules=query%2B*'
463 => 'apihelp-paraminfo-example-2',
467 public function getHelpUrls() {
468 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parameter_information';
472 /** @deprecated class alias since 1.43 */
473 class_alias( ApiParamInfo::class, 'ApiParamInfo' );