Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 265
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiParamInfo
0.00% covered (danger)
0.00%
0 / 265
0.00% covered (danger)
0.00%
0 / 9
5852
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 1
812
 listAllSubmodules
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 formatHelpMessages
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
132
 getModuleInfo
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 1
756
 isReadMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2008 Roan Kattouw <roan.kattouw@gmail.com>
4 *
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.
9 *
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.
14 *
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
19 *
20 * @file
21 */
22
23use MediaWiki\Context\RequestContext;
24use MediaWiki\Parser\Parser;
25use MediaWiki\SpecialPage\SpecialPage;
26use MediaWiki\User\UserFactory;
27use MediaWiki\Utils\ExtensionInfo;
28use Wikimedia\ParamValidator\ParamValidator;
29
30/**
31 * @ingroup API
32 */
33class ApiParamInfo extends ApiBase {
34
35    private $helpFormat;
36
37    /** @var RequestContext */
38    private $context;
39
40    /** @var UserFactory */
41    private $userFactory;
42
43    /**
44     * @param ApiMain $main
45     * @param string $action
46     * @param UserFactory $userFactory
47     */
48    public function __construct(
49        ApiMain $main,
50        $action,
51        UserFactory $userFactory
52    ) {
53        parent::__construct( $main, $action );
54        $this->userFactory = $userFactory;
55    }
56
57    public function execute() {
58        // Get parameters
59        $params = $this->extractRequestParams();
60
61        $this->helpFormat = $params['helpformat'];
62        $this->context = new RequestContext;
63        $this->context->setUser( $this->userFactory->newAnonymous() ); // anon to avoid caching issues
64        $this->context->setLanguage( $this->getMain()->getLanguage() );
65
66        if ( is_array( $params['modules'] ) ) {
67            $modules = [];
68            foreach ( $params['modules'] as $path ) {
69                if ( $path === '*' || $path === '**' ) {
70                    $path = "main+$path";
71                }
72                if ( str_ends_with( $path, '+*' ) || str_ends_with( $path, ' *' ) ) {
73                    $submodules = true;
74                    $path = substr( $path, 0, -2 );
75                    $recursive = false;
76                } elseif ( str_ends_with( $path, '+**' ) || str_ends_with( $path, ' **' ) ) {
77                    $submodules = true;
78                    $path = substr( $path, 0, -3 );
79                    $recursive = true;
80                } else {
81                    $submodules = false;
82                }
83
84                if ( $submodules ) {
85                    try {
86                        $module = $this->getModuleFromPath( $path );
87                    } catch ( ApiUsageException $ex ) {
88                        foreach ( $ex->getStatusValue()->getErrors() as $error ) {
89                            $this->addWarning( $error );
90                        }
91                        continue;
92                    }
93                    // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
94                    // recursive is set when used
95                    $submodules = $this->listAllSubmodules( $module, $recursive );
96                    if ( $submodules ) {
97                        $modules = array_merge( $modules, $submodules );
98                    } else {
99                        $this->addWarning( [ 'apierror-badmodule-nosubmodules', $path ], 'badmodule' );
100                    }
101                } else {
102                    $modules[] = $path;
103                }
104            }
105        } else {
106            $modules = [];
107        }
108
109        if ( is_array( $params['querymodules'] ) ) {
110            $queryModules = $params['querymodules'];
111            foreach ( $queryModules as $m ) {
112                $modules[] = 'query+' . $m;
113            }
114        } else {
115            $queryModules = [];
116        }
117
118        if ( is_array( $params['formatmodules'] ) ) {
119            $formatModules = $params['formatmodules'];
120            foreach ( $formatModules as $m ) {
121                $modules[] = $m;
122            }
123        } else {
124            $formatModules = [];
125        }
126
127        $modules = array_unique( $modules );
128
129        $res = [];
130
131        foreach ( $modules as $m ) {
132            try {
133                $module = $this->getModuleFromPath( $m );
134            } catch ( ApiUsageException $ex ) {
135                foreach ( $ex->getStatusValue()->getErrors() as $error ) {
136                    $this->addWarning( $error );
137                }
138                continue;
139            }
140            $key = 'modules';
141
142            // Back compat
143            $isBCQuery = false;
144            if ( $module->getParent() && $module->getParent()->getModuleName() == 'query' &&
145                in_array( $module->getModuleName(), $queryModules )
146            ) {
147                $isBCQuery = true;
148                $key = 'querymodules';
149            }
150            if ( in_array( $module->getModuleName(), $formatModules ) ) {
151                $key = 'formatmodules';
152            }
153
154            $item = $this->getModuleInfo( $module );
155            if ( $isBCQuery ) {
156                $item['querytype'] = $item['group'];
157            }
158            $res[$key][] = $item;
159        }
160
161        $result = $this->getResult();
162        $result->addValue( [ $this->getModuleName() ], 'helpformat', $this->helpFormat );
163
164        foreach ( $res as $key => $stuff ) {
165            ApiResult::setIndexedTagName( $res[$key], 'module' );
166        }
167
168        if ( $params['mainmodule'] ) {
169            $res['mainmodule'] = $this->getModuleInfo( $this->getMain() );
170        }
171
172        if ( $params['pagesetmodule'] ) {
173            $pageSet = new ApiPageSet( $this->getMain()->getModuleManager()->getModule( 'query' ) );
174            $res['pagesetmodule'] = $this->getModuleInfo( $pageSet );
175            unset( $res['pagesetmodule']['name'] );
176            unset( $res['pagesetmodule']['path'] );
177            unset( $res['pagesetmodule']['group'] );
178        }
179
180        $result->addValue( null, $this->getModuleName(), $res );
181    }
182
183    /**
184     * List all submodules of a module
185     * @param ApiBase $module
186     * @param bool $recursive
187     * @return string[]
188     */
189    private function listAllSubmodules( ApiBase $module, $recursive ) {
190        $paths = [];
191        $manager = $module->getModuleManager();
192        if ( $manager ) {
193            $names = $manager->getNames();
194            sort( $names );
195            foreach ( $names as $name ) {
196                $submodule = $manager->getModule( $name );
197                $paths[] = $submodule->getModulePath();
198                if ( $recursive && $submodule->getModuleManager() ) {
199                    $paths = array_merge( $paths, $this->listAllSubmodules( $submodule, $recursive ) );
200                }
201            }
202        }
203        return $paths;
204    }
205
206    /**
207     * @param array &$res Result array
208     * @param string $key Result key
209     * @param Message[] $msgs
210     * @param bool $joinLists
211     */
212    protected function formatHelpMessages( array &$res, $key, array $msgs, $joinLists = false ) {
213        switch ( $this->helpFormat ) {
214            case 'none':
215                break;
216
217            case 'wikitext':
218                $ret = [];
219                foreach ( $msgs as $m ) {
220                    $ret[] = $m->setContext( $this->context )->text();
221                }
222                $res[$key] = implode( "\n\n", $ret );
223                if ( $joinLists ) {
224                    $res[$key] = preg_replace( '!^(([*#:;])[^\n]*)\n\n(?=\2)!m', "$1\n", $res[$key] );
225                }
226                break;
227
228            case 'html':
229                $ret = [];
230                foreach ( $msgs as $m ) {
231                    $ret[] = $m->setContext( $this->context )->parseAsBlock();
232                }
233                $ret = implode( "\n", $ret );
234                if ( $joinLists ) {
235                    $ret = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $ret );
236                }
237                $res[$key] = Parser::stripOuterParagraph( $ret );
238                break;
239
240            case 'raw':
241                $res[$key] = [];
242                foreach ( $msgs as $m ) {
243                    $a = [
244                        'key' => $m->getKey(),
245                        'params' => $m->getParams(),
246                    ];
247                    ApiResult::setIndexedTagName( $a['params'], 'param' );
248                    if ( $m instanceof ApiHelpParamValueMessage ) {
249                        $a['forvalue'] = $m->getParamValue();
250                    }
251                    $res[$key][] = $a;
252                }
253                ApiResult::setIndexedTagName( $res[$key], 'msg' );
254                break;
255        }
256    }
257
258    /**
259     * @param ApiBase $module
260     * @return array
261     */
262    private function getModuleInfo( $module ) {
263        $ret = [];
264        $path = $module->getModulePath();
265        $paramValidator = $module->getMain()->getParamValidator();
266
267        $ret['name'] = $module->getModuleName();
268        $ret['classname'] = get_class( $module );
269        $ret['path'] = $path;
270        if ( !$module->isMain() ) {
271            $ret['group'] = $module->getParent()->getModuleManager()->getModuleGroup(
272                $module->getModuleName()
273            );
274        }
275        $ret['prefix'] = $module->getModulePrefix();
276
277        $sourceInfo = $module->getModuleSourceInfo();
278        if ( $sourceInfo ) {
279            $ret['source'] = $sourceInfo['name'];
280            if ( isset( $sourceInfo['namemsg'] ) ) {
281                $ret['sourcename'] = $this->context->msg( $sourceInfo['namemsg'] )->text();
282            } else {
283                $ret['sourcename'] = $ret['source'];
284            }
285
286            $link = SpecialPage::getTitleFor( 'Version', 'License/' . $sourceInfo['name'] )->getFullURL();
287            if ( isset( $sourceInfo['license-name'] ) ) {
288                $ret['licensetag'] = $sourceInfo['license-name'];
289                $ret['licenselink'] = (string)$link;
290            } elseif ( ExtensionInfo::getLicenseFileNames( dirname( $sourceInfo['path'] ) ) ) {
291                $ret['licenselink'] = (string)$link;
292            }
293        }
294
295        $this->formatHelpMessages( $ret, 'description', $module->getFinalDescription() );
296
297        foreach ( $module->getHelpFlags() as $flag ) {
298            $ret[$flag] = true;
299        }
300
301        $ret['helpurls'] = (array)$module->getHelpUrls();
302        if ( isset( $ret['helpurls'][0] ) && $ret['helpurls'][0] === false ) {
303            $ret['helpurls'] = [];
304        }
305        // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
306        ApiResult::setIndexedTagName( $ret['helpurls'], 'helpurl' );
307
308        if ( $this->helpFormat !== 'none' ) {
309            $ret['examples'] = [];
310            $examples = $module->getExamplesMessages();
311            foreach ( $examples as $qs => $msg ) {
312                $item = [
313                    'query' => $qs
314                ];
315                $msg = ApiBase::makeMessage( $msg, $this->context, [
316                    $module->getModulePrefix(),
317                    $module->getModuleName(),
318                    $module->getModulePath()
319                ] );
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' );
326                    }
327                }
328                $ret['examples'][] = $item;
329            }
330            ApiResult::setIndexedTagName( $ret['examples'], 'example' );
331        }
332
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 );
340
341            $item = [
342                'index' => ++$index,
343                'name' => $name,
344            ];
345
346            if ( !empty( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ) {
347                $item['templatevars'] = $settings[ApiBase::PARAM_TEMPLATE_VARS];
348                ApiResult::setIndexedTagName( $item['templatevars'], 'var' );
349            }
350
351            if ( isset( $paramDesc[$name] ) ) {
352                $this->formatHelpMessages( $item, 'description', $paramDesc[$name], true );
353            }
354
355            foreach ( $paramValidator->getParamInfo( $module, $name, $settings, [] ) as $k => $v ) {
356                $item[$k] = $v;
357            }
358
359            if ( $name === 'token' && $module->needsToken() ) {
360                $item['tokentype'] = $module->needsToken();
361            }
362
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' );
369            }
370
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,
377                    ];
378                    if ( count( $i ) ) {
379                        $info['values'] = $i;
380                        ApiResult::setIndexedTagName( $info['values'], 'v' );
381                    }
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;
390                }
391                ApiResult::setIndexedTagName( $item['info'], 'i' );
392            }
393
394            $key = empty( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ? 'parameters' : 'templatedparameters';
395            $ret[$key][] = $item;
396        }
397        ApiResult::setIndexedTagName( $ret['parameters'], 'param' );
398        ApiResult::setIndexedTagName( $ret['templatedparameters'], 'param' );
399
400        $dynamicParams = $module->dynamicParameterDocumentation();
401        if ( $dynamicParams !== null ) {
402            if ( $this->helpFormat === 'none' ) {
403                $ret['dynamicparameters'] = true;
404            } else {
405                $dynamicParams = ApiBase::makeMessage( $dynamicParams, $this->context, [
406                    $module->getModulePrefix(),
407                    $module->getModuleName(),
408                    $module->getModulePath()
409                ] );
410                $this->formatHelpMessages( $ret, 'dynamicparameters', [ $dynamicParams ] );
411            }
412        }
413
414        return $ret;
415    }
416
417    public function isReadMode() {
418        return false;
419    }
420
421    public function getAllowedParams() {
422        // back compat
423        $querymodules = $this->getMain()->getModuleManager()
424            ->getModule( 'query' )->getModuleManager()->getNames();
425        sort( $querymodules );
426        $formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
427        sort( $formatmodules );
428
429        return [
430            'modules' => [
431                ParamValidator::PARAM_ISMULTI => true,
432            ],
433            'helpformat' => [
434                ParamValidator::PARAM_DEFAULT => 'none',
435                ParamValidator::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
436            ],
437
438            'querymodules' => [
439                ParamValidator::PARAM_DEPRECATED => true,
440                ParamValidator::PARAM_ISMULTI => true,
441                ParamValidator::PARAM_TYPE => $querymodules,
442            ],
443            'mainmodule' => [
444                ParamValidator::PARAM_DEPRECATED => true,
445            ],
446            'pagesetmodule' => [
447                ParamValidator::PARAM_DEPRECATED => true,
448            ],
449            'formatmodules' => [
450                ParamValidator::PARAM_DEPRECATED => true,
451                ParamValidator::PARAM_ISMULTI => true,
452                ParamValidator::PARAM_TYPE => $formatmodules,
453            ]
454        ];
455    }
456
457    protected function getExamplesMessages() {
458        return [
459            'action=paraminfo&modules=parse|phpfm|query%2Ballpages|query%2Bsiteinfo'
460                => 'apihelp-paraminfo-example-1',
461            'action=paraminfo&modules=query%2B*'
462                => 'apihelp-paraminfo-example-2',
463        ];
464    }
465
466    public function getHelpUrls() {
467        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parameter_information';
468    }
469}