Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 449 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
ApiHelp | |
0.00% |
0 / 449 |
|
0.00% |
0 / 12 |
8190 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
20 | |||
getHelp | |
0.00% |
0 / 46 |
|
0.00% |
0 / 1 |
240 | |||
fixHelpLinks | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
56 | |||
wrap | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getHelpInternal | |
0.00% |
0 / 294 |
|
0.00% |
0 / 1 |
3080 | |||
shouldCheckMaxlag | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isReadMode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCustomPrinter | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getAllowedParams | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getExamplesMessages | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Copyright © 2014 Wikimedia Foundation and contributors |
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 | |
23 | use MediaWiki\Context\DerivativeContext; |
24 | use MediaWiki\Context\IContextSource; |
25 | use MediaWiki\Html\Html; |
26 | use MediaWiki\Html\HtmlHelper; |
27 | use MediaWiki\MainConfigNames; |
28 | use MediaWiki\MediaWikiServices; |
29 | use MediaWiki\Output\OutputPage; |
30 | use MediaWiki\Parser\ParserOutput; |
31 | use MediaWiki\Parser\ParserOutputFlags; |
32 | use MediaWiki\SpecialPage\SpecialPage; |
33 | use MediaWiki\Specials\SpecialVersion; |
34 | use MediaWiki\Title\Title; |
35 | use MediaWiki\Utils\ExtensionInfo; |
36 | use Wikimedia\ParamValidator\ParamValidator; |
37 | use Wikimedia\Parsoid\Core\TOCData; |
38 | use Wikimedia\RemexHtml\Serializer\SerializerNode; |
39 | |
40 | /** |
41 | * Class to output help for an API module |
42 | * |
43 | * @since 1.25 completely rewritten |
44 | * @ingroup API |
45 | */ |
46 | class ApiHelp extends ApiBase { |
47 | private SkinFactory $skinFactory; |
48 | |
49 | /** |
50 | * @param ApiMain $main |
51 | * @param string $action |
52 | * @param SkinFactory $skinFactory |
53 | */ |
54 | public function __construct( |
55 | ApiMain $main, |
56 | $action, |
57 | SkinFactory $skinFactory |
58 | ) { |
59 | parent::__construct( $main, $action ); |
60 | $this->skinFactory = $skinFactory; |
61 | } |
62 | |
63 | public function execute() { |
64 | $params = $this->extractRequestParams(); |
65 | $modules = []; |
66 | |
67 | foreach ( $params['modules'] as $path ) { |
68 | $modules[] = $this->getModuleFromPath( $path ); |
69 | } |
70 | |
71 | // Get the help |
72 | $context = new DerivativeContext( $this->getMain()->getContext() ); |
73 | $context->setSkin( $this->skinFactory->makeSkin( 'apioutput' ) ); |
74 | $context->setLanguage( $this->getMain()->getLanguage() ); |
75 | $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) ); |
76 | $out = new OutputPage( $context ); |
77 | $out->setRobotPolicy( 'noindex,nofollow' ); |
78 | $out->setCopyrightUrl( 'https://www.mediawiki.org/wiki/Special:MyLanguage/Copyright' ); |
79 | $out->disallowUserJs(); |
80 | $context->setOutput( $out ); |
81 | |
82 | self::getHelp( $context, $modules, $params ); |
83 | |
84 | // Grab the output from the skin |
85 | ob_start(); |
86 | $context->getOutput()->output(); |
87 | $html = ob_get_clean(); |
88 | |
89 | $result = $this->getResult(); |
90 | if ( $params['wrap'] ) { |
91 | $data = [ |
92 | 'mime' => 'text/html', |
93 | 'filename' => 'api-help.html', |
94 | 'help' => $html, |
95 | ]; |
96 | ApiResult::setSubelementsList( $data, 'help' ); |
97 | $result->addValue( null, $this->getModuleName(), $data ); |
98 | } else { |
99 | // Show any errors at the top of the HTML |
100 | $transform = [ |
101 | 'Types' => [ 'AssocAsObject' => true ], |
102 | 'Strip' => 'all', |
103 | ]; |
104 | $errors = array_filter( [ |
105 | 'errors' => $this->getResult()->getResultData( [ 'errors' ], $transform ), |
106 | 'warnings' => $this->getResult()->getResultData( [ 'warnings' ], $transform ), |
107 | ] ); |
108 | if ( $errors ) { |
109 | $json = FormatJson::encode( $errors, true, FormatJson::UTF8_OK ); |
110 | // Escape any "--", some parsers might interpret that as end-of-comment. |
111 | // The above already escaped any "<" and ">". |
112 | $json = str_replace( '--', '-\u002D', $json ); |
113 | $html = "<!-- API warnings and errors:\n$json\n-->\n$html"; |
114 | } |
115 | |
116 | $result->reset(); |
117 | $result->addValue( null, 'text', $html, ApiResult::NO_SIZE_CHECK ); |
118 | $result->addValue( null, 'mime', 'text/html', ApiResult::NO_SIZE_CHECK ); |
119 | $result->addValue( null, 'filename', 'api-help.html', ApiResult::NO_SIZE_CHECK ); |
120 | } |
121 | } |
122 | |
123 | /** |
124 | * Generate help for the specified modules |
125 | * |
126 | * Help is placed into the OutputPage object returned by |
127 | * $context->getOutput(). |
128 | * |
129 | * Recognized options include: |
130 | * - headerlevel: (int) Header tag level |
131 | * - nolead: (bool) Skip the inclusion of api-help-lead |
132 | * - noheader: (bool) Skip the inclusion of the top-level section headers |
133 | * - submodules: (bool) Include help for submodules of the current module |
134 | * - recursivesubmodules: (bool) Include help for submodules recursively |
135 | * - helptitle: (string) Title to link for additional modules' help. Should contain $1. |
136 | * - toc: (bool) Include a table of contents |
137 | * |
138 | * @param IContextSource $context |
139 | * @param ApiBase[]|ApiBase $modules |
140 | * @param array $options Formatting options (described above) |
141 | */ |
142 | public static function getHelp( IContextSource $context, $modules, array $options ) { |
143 | if ( !is_array( $modules ) ) { |
144 | $modules = [ $modules ]; |
145 | } |
146 | |
147 | $out = $context->getOutput(); |
148 | $out->addModuleStyles( [ |
149 | 'mediawiki.hlist', |
150 | 'mediawiki.apipretty', |
151 | ] ); |
152 | $out->setPageTitleMsg( $context->msg( 'api-help-title' ) ); |
153 | |
154 | $services = MediaWikiServices::getInstance(); |
155 | $cache = $services->getMainWANObjectCache(); |
156 | $cacheKey = null; |
157 | if ( count( $modules ) == 1 && $modules[0] instanceof ApiMain && |
158 | $options['recursivesubmodules'] && |
159 | $context->getLanguage()->equals( $services->getContentLanguage() ) |
160 | ) { |
161 | $cacheHelpTimeout = $context->getConfig()->get( MainConfigNames::APICacheHelpTimeout ); |
162 | if ( $cacheHelpTimeout > 0 ) { |
163 | // Get help text from cache if present |
164 | $cacheKey = $cache->makeKey( 'apihelp', $modules[0]->getModulePath(), |
165 | (int)!empty( $options['toc'] ), |
166 | str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) ); |
167 | $cached = $cache->get( $cacheKey ); |
168 | if ( $cached ) { |
169 | $out->addHTML( $cached ); |
170 | return; |
171 | } |
172 | } |
173 | } |
174 | if ( $out->getHTML() !== '' ) { |
175 | // Don't save to cache, there's someone else's content in the page |
176 | // already |
177 | $cacheKey = null; |
178 | } |
179 | |
180 | $options['recursivesubmodules'] = !empty( $options['recursivesubmodules'] ); |
181 | $options['submodules'] = $options['recursivesubmodules'] || !empty( $options['submodules'] ); |
182 | |
183 | // Prepend lead |
184 | if ( empty( $options['nolead'] ) ) { |
185 | $msg = $context->msg( 'api-help-lead' ); |
186 | if ( !$msg->isDisabled() ) { |
187 | $out->addHTML( $msg->parseAsBlock() ); |
188 | } |
189 | } |
190 | |
191 | $haveModules = []; |
192 | $html = self::getHelpInternal( $context, $modules, $options, $haveModules ); |
193 | if ( !empty( $options['toc'] ) && $haveModules ) { |
194 | $pout = new ParserOutput; |
195 | $pout->setTOCData( TOCData::fromLegacy( array_values( $haveModules ) ) ); |
196 | $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC ); |
197 | $pout->setText( Parser::TOC_PLACEHOLDER ); |
198 | $out->addParserOutput( $pout ); |
199 | } |
200 | $out->addHTML( $html ); |
201 | |
202 | $helptitle = $options['helptitle'] ?? null; |
203 | $html = self::fixHelpLinks( $out->getHTML(), $helptitle, $haveModules ); |
204 | $out->clearHTML(); |
205 | $out->addHTML( $html ); |
206 | |
207 | if ( $cacheKey !== null ) { |
208 | // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $cacheHelpTimeout declared when $cacheKey is set |
209 | $cache->set( $cacheKey, $out->getHTML(), $cacheHelpTimeout ); |
210 | } |
211 | } |
212 | |
213 | /** |
214 | * Replace Special:ApiHelp links with links to api.php |
215 | * |
216 | * @param string $html |
217 | * @param string|null $helptitle Title to link to rather than api.php, must contain '$1' |
218 | * @param array $localModules Keys are modules to link within the current page, values are ignored |
219 | * @return string |
220 | */ |
221 | public static function fixHelpLinks( $html, $helptitle = null, $localModules = [] ) { |
222 | return HtmlHelper::modifyElements( |
223 | $html, |
224 | static function ( SerializerNode $node ): bool { |
225 | return $node->name === 'a' |
226 | && isset( $node->attrs['href'] ) |
227 | && !str_contains( $node->attrs['class'] ?? '', 'apihelp-linktrail' ); |
228 | }, |
229 | static function ( SerializerNode $node ) use ( $helptitle, $localModules ): SerializerNode { |
230 | $href = $node->attrs['href']; |
231 | // FIXME This can't be right to do this in a loop |
232 | do { |
233 | $old = $href; |
234 | $href = rawurldecode( $href ); |
235 | } while ( $old !== $href ); |
236 | if ( preg_match( '!Special:ApiHelp/([^&/|#]+)((?:#.*)?)!', $href, $m ) ) { |
237 | if ( isset( $localModules[$m[1]] ) ) { |
238 | $href = $m[2] === '' ? '#' . $m[1] : $m[2]; |
239 | } elseif ( $helptitle !== null ) { |
240 | $href = Title::newFromText( str_replace( '$1', $m[1], $helptitle ) . $m[2] ) |
241 | ->getFullURL(); |
242 | } else { |
243 | $href = wfAppendQuery( wfScript( 'api' ), [ |
244 | 'action' => 'help', |
245 | 'modules' => $m[1], |
246 | ] ) . $m[2]; |
247 | } |
248 | $node->attrs['href'] = $href; |
249 | unset( $node->attrs['title'] ); |
250 | } |
251 | |
252 | return $node; |
253 | } |
254 | ); |
255 | } |
256 | |
257 | /** |
258 | * Wrap a message in HTML with a class. |
259 | * |
260 | * @param Message $msg |
261 | * @param string $class |
262 | * @param string $tag |
263 | * @return string |
264 | */ |
265 | private static function wrap( Message $msg, $class, $tag = 'span' ) { |
266 | return Html::rawElement( $tag, [ 'class' => $class ], |
267 | $msg->parse() |
268 | ); |
269 | } |
270 | |
271 | /** |
272 | * Recursively-called function to actually construct the help |
273 | * |
274 | * @param IContextSource $context |
275 | * @param ApiBase[] $modules |
276 | * @param array $options |
277 | * @param array &$haveModules |
278 | * @return string |
279 | */ |
280 | private static function getHelpInternal( IContextSource $context, array $modules, |
281 | array $options, &$haveModules |
282 | ) { |
283 | $out = ''; |
284 | |
285 | $level = empty( $options['headerlevel'] ) ? 2 : $options['headerlevel']; |
286 | if ( empty( $options['tocnumber'] ) ) { |
287 | $tocnumber = [ 2 => 0 ]; |
288 | } else { |
289 | $tocnumber = &$options['tocnumber']; |
290 | } |
291 | |
292 | foreach ( $modules as $module ) { |
293 | $paramValidator = $module->getMain()->getParamValidator(); |
294 | $tocnumber[$level]++; |
295 | $path = $module->getModulePath(); |
296 | $module->setContext( $context ); |
297 | $help = [ |
298 | 'header' => '', |
299 | 'flags' => '', |
300 | 'description' => '', |
301 | 'help-urls' => '', |
302 | 'parameters' => '', |
303 | 'examples' => '', |
304 | 'submodules' => '', |
305 | ]; |
306 | |
307 | if ( empty( $options['noheader'] ) || !empty( $options['toc'] ) ) { |
308 | $anchor = $path; |
309 | $i = 1; |
310 | while ( isset( $haveModules[$anchor] ) ) { |
311 | $anchor = $path . '|' . ++$i; |
312 | } |
313 | |
314 | if ( $module->isMain() ) { |
315 | $headerContent = $context->msg( 'api-help-main-header' )->parse(); |
316 | $headerAttr = [ |
317 | 'class' => 'apihelp-header', |
318 | ]; |
319 | } else { |
320 | $name = $module->getModuleName(); |
321 | $headerContent = htmlspecialchars( |
322 | $module->getParent()->getModuleManager()->getModuleGroup( $name ) . "=$name" |
323 | ); |
324 | if ( $module->getModulePrefix() !== '' ) { |
325 | $headerContent .= ' ' . |
326 | $context->msg( 'parentheses', $module->getModulePrefix() )->parse(); |
327 | } |
328 | // Module names are always in English and not localized, |
329 | // so English language and direction must be set explicitly, |
330 | // otherwise parentheses will get broken in RTL wikis |
331 | $headerAttr = [ |
332 | 'class' => [ 'apihelp-header', 'apihelp-module-name' ], |
333 | 'dir' => 'ltr', |
334 | 'lang' => 'en', |
335 | ]; |
336 | } |
337 | |
338 | $headerAttr['id'] = $anchor; |
339 | |
340 | // T326687: Maybe transition to using a SectionMetadata object? |
341 | $haveModules[$anchor] = [ |
342 | 'toclevel' => count( $tocnumber ), |
343 | 'level' => $level, |
344 | 'anchor' => $anchor, |
345 | 'line' => $headerContent, |
346 | 'number' => implode( '.', $tocnumber ), |
347 | 'index' => '', |
348 | ]; |
349 | if ( empty( $options['noheader'] ) ) { |
350 | $help['header'] .= Html::rawElement( |
351 | 'h' . min( 6, $level ), |
352 | $headerAttr, |
353 | $headerContent |
354 | ); |
355 | } |
356 | } else { |
357 | $haveModules[$path] = true; |
358 | } |
359 | |
360 | $links = []; |
361 | $any = false; |
362 | for ( $m = $module; $m !== null; $m = $m->getParent() ) { |
363 | $name = $m->getModuleName(); |
364 | if ( $name === 'main_int' ) { |
365 | $name = 'main'; |
366 | } |
367 | |
368 | if ( count( $modules ) === 1 && $m === $modules[0] && |
369 | !( !empty( $options['submodules'] ) && $m->getModuleManager() ) |
370 | ) { |
371 | $link = Html::element( 'b', [ 'dir' => 'ltr', 'lang' => 'en' ], $name ); |
372 | } else { |
373 | $link = SpecialPage::getTitleFor( 'ApiHelp', $m->getModulePath() )->getLocalURL(); |
374 | $link = Html::element( 'a', |
375 | [ 'href' => $link, 'class' => 'apihelp-linktrail', 'dir' => 'ltr', 'lang' => 'en' ], |
376 | $name |
377 | ); |
378 | $any = true; |
379 | } |
380 | array_unshift( $links, $link ); |
381 | } |
382 | if ( $any ) { |
383 | $help['header'] .= self::wrap( |
384 | $context->msg( 'parentheses' ) |
385 | ->rawParams( $context->getLanguage()->pipeList( $links ) ), |
386 | 'apihelp-linktrail', 'div' |
387 | ); |
388 | } |
389 | |
390 | $flags = $module->getHelpFlags(); |
391 | $help['flags'] .= Html::openElement( 'div', |
392 | [ 'class' => [ 'apihelp-block', 'apihelp-flags' ] ] ); |
393 | $msg = $context->msg( 'api-help-flags' ); |
394 | if ( !$msg->isDisabled() ) { |
395 | $help['flags'] .= self::wrap( |
396 | $msg->numParams( count( $flags ) ), 'apihelp-block-head', 'div' |
397 | ); |
398 | } |
399 | $help['flags'] .= Html::openElement( 'ul' ); |
400 | foreach ( $flags as $flag ) { |
401 | $help['flags'] .= Html::rawElement( 'li', [], |
402 | // The follow classes are used here: |
403 | // * apihelp-flag-generator |
404 | // * apihelp-flag-internal |
405 | // * apihelp-flag-mustbeposted |
406 | // * apihelp-flag-readrights |
407 | // * apihelp-flag-writerights |
408 | self::wrap( $context->msg( "api-help-flag-$flag" ), "apihelp-flag-$flag" ) |
409 | ); |
410 | } |
411 | $sourceInfo = $module->getModuleSourceInfo(); |
412 | if ( $sourceInfo ) { |
413 | if ( isset( $sourceInfo['namemsg'] ) ) { |
414 | $extname = $context->msg( $sourceInfo['namemsg'] )->text(); |
415 | } else { |
416 | // Probably English, so wrap it. |
417 | $extname = Html::element( 'span', [ 'dir' => 'ltr', 'lang' => 'en' ], $sourceInfo['name'] ); |
418 | } |
419 | $help['flags'] .= Html::rawElement( 'li', [], |
420 | self::wrap( |
421 | $context->msg( 'api-help-source', $extname, $sourceInfo['name'] ), |
422 | 'apihelp-source' |
423 | ) |
424 | ); |
425 | |
426 | $link = SpecialPage::getTitleFor( 'Version', 'License/' . $sourceInfo['name'] ); |
427 | if ( isset( $sourceInfo['license-name'] ) ) { |
428 | $msg = $context->msg( 'api-help-license', $link, |
429 | Html::element( 'span', [ 'dir' => 'ltr', 'lang' => 'en' ], $sourceInfo['license-name'] ) |
430 | ); |
431 | } elseif ( ExtensionInfo::getLicenseFileNames( dirname( $sourceInfo['path'] ) ) ) { |
432 | $msg = $context->msg( 'api-help-license-noname', $link ); |
433 | } else { |
434 | $msg = $context->msg( 'api-help-license-unknown' ); |
435 | } |
436 | $help['flags'] .= Html::rawElement( 'li', [], |
437 | self::wrap( $msg, 'apihelp-license' ) |
438 | ); |
439 | } else { |
440 | $help['flags'] .= Html::rawElement( 'li', [], |
441 | self::wrap( $context->msg( 'api-help-source-unknown' ), 'apihelp-source' ) |
442 | ); |
443 | $help['flags'] .= Html::rawElement( 'li', [], |
444 | self::wrap( $context->msg( 'api-help-license-unknown' ), 'apihelp-license' ) |
445 | ); |
446 | } |
447 | $help['flags'] .= Html::closeElement( 'ul' ); |
448 | $help['flags'] .= Html::closeElement( 'div' ); |
449 | |
450 | foreach ( $module->getFinalDescription() as $msg ) { |
451 | $msg->setContext( $context ); |
452 | $help['description'] .= $msg->parseAsBlock(); |
453 | } |
454 | |
455 | $urls = $module->getHelpUrls(); |
456 | if ( $urls ) { |
457 | if ( !is_array( $urls ) ) { |
458 | $urls = [ $urls ]; |
459 | } |
460 | $help['help-urls'] .= Html::openElement( 'div', |
461 | [ 'class' => [ 'apihelp-block', 'apihelp-help-urls' ] ] |
462 | ); |
463 | $msg = $context->msg( 'api-help-help-urls' ); |
464 | if ( !$msg->isDisabled() ) { |
465 | $help['help-urls'] .= self::wrap( |
466 | $msg->numParams( count( $urls ) ), 'apihelp-block-head', 'div' |
467 | ); |
468 | } |
469 | $help['help-urls'] .= Html::openElement( 'ul' ); |
470 | foreach ( $urls as $url ) { |
471 | $help['help-urls'] .= Html::rawElement( 'li', [], |
472 | Html::element( 'a', [ 'href' => $url, 'dir' => 'ltr' ], $url ) |
473 | ); |
474 | } |
475 | $help['help-urls'] .= Html::closeElement( 'ul' ); |
476 | $help['help-urls'] .= Html::closeElement( 'div' ); |
477 | } |
478 | |
479 | $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP ); |
480 | $dynamicParams = $module->dynamicParameterDocumentation(); |
481 | $groups = []; |
482 | if ( $params || $dynamicParams !== null ) { |
483 | $help['parameters'] .= Html::openElement( 'div', |
484 | [ 'class' => [ 'apihelp-block', 'apihelp-parameters' ] ] |
485 | ); |
486 | $msg = $context->msg( 'api-help-parameters' ); |
487 | if ( !$msg->isDisabled() ) { |
488 | $help['parameters'] .= self::wrap( |
489 | $msg->numParams( count( $params ) ), 'apihelp-block-head', 'div' |
490 | ); |
491 | if ( !$module->isMain() ) { |
492 | // Add a note explaining that other parameters may exist. |
493 | $help['parameters'] .= self::wrap( |
494 | $context->msg( 'api-help-parameters-note' ), 'apihelp-block-header', 'div' |
495 | ); |
496 | } |
497 | } |
498 | $help['parameters'] .= Html::openElement( 'dl' ); |
499 | |
500 | $descriptions = $module->getFinalParamDescription(); |
501 | |
502 | foreach ( $params as $name => $settings ) { |
503 | $settings = $paramValidator->normalizeSettings( $settings ); |
504 | |
505 | if ( $settings[ParamValidator::PARAM_TYPE] === 'submodule' ) { |
506 | $groups[] = $name; |
507 | } |
508 | |
509 | $encodedParamName = $module->encodeParamName( $name ); |
510 | $paramNameAttribs = [ 'dir' => 'ltr', 'lang' => 'en' ]; |
511 | if ( isset( $anchor ) ) { |
512 | $paramNameAttribs['id'] = "$anchor:$encodedParamName"; |
513 | } |
514 | $help['parameters'] .= Html::rawElement( 'dt', [], |
515 | Html::element( 'span', $paramNameAttribs, $encodedParamName ) |
516 | ); |
517 | |
518 | // Add description |
519 | $description = []; |
520 | if ( isset( $descriptions[$name] ) ) { |
521 | foreach ( $descriptions[$name] as $msg ) { |
522 | $msg->setContext( $context ); |
523 | $description[] = $msg->parseAsBlock(); |
524 | } |
525 | } |
526 | if ( !array_filter( $description ) ) { |
527 | $description = [ self::wrap( |
528 | $context->msg( 'api-help-param-no-description' ), |
529 | 'apihelp-empty' |
530 | ) ]; |
531 | } |
532 | |
533 | // Add "deprecated" flag |
534 | if ( !empty( $settings[ParamValidator::PARAM_DEPRECATED] ) ) { |
535 | $help['parameters'] .= Html::openElement( 'dd', |
536 | [ 'class' => 'info' ] ); |
537 | $help['parameters'] .= self::wrap( |
538 | $context->msg( 'api-help-param-deprecated' ), |
539 | 'apihelp-deprecated', 'strong' |
540 | ); |
541 | $help['parameters'] .= Html::closeElement( 'dd' ); |
542 | } |
543 | |
544 | if ( $description ) { |
545 | $description = implode( '', $description ); |
546 | $description = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $description ); |
547 | $help['parameters'] .= Html::rawElement( 'dd', |
548 | [ 'class' => 'description' ], $description ); |
549 | } |
550 | |
551 | // Add usage info |
552 | $info = []; |
553 | $paramHelp = $paramValidator->getHelpInfo( $module, $name, $settings, [] ); |
554 | |
555 | unset( $paramHelp[ParamValidator::PARAM_DEPRECATED] ); |
556 | |
557 | if ( isset( $paramHelp[ParamValidator::PARAM_REQUIRED] ) ) { |
558 | $paramHelp[ParamValidator::PARAM_REQUIRED]->setContext( $context ); |
559 | $info[] = $paramHelp[ParamValidator::PARAM_REQUIRED]; |
560 | unset( $paramHelp[ParamValidator::PARAM_REQUIRED] ); |
561 | } |
562 | |
563 | // Custom info? |
564 | if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) { |
565 | foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) { |
566 | $tag = array_shift( $i ); |
567 | $info[] = $context->msg( "apihelp-{$path}-paraminfo-{$tag}" ) |
568 | ->numParams( count( $i ) ) |
569 | ->params( $context->getLanguage()->commaList( $i ) ) |
570 | ->params( $module->getModulePrefix() ) |
571 | ->parse(); |
572 | } |
573 | } |
574 | |
575 | // Templated? |
576 | if ( !empty( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ) { |
577 | $vars = []; |
578 | $msg = 'api-help-param-templated-var-first'; |
579 | foreach ( $settings[ApiBase::PARAM_TEMPLATE_VARS] as $k => $v ) { |
580 | $vars[] = $context->msg( $msg, $k, $module->encodeParamName( $v ) ); |
581 | $msg = 'api-help-param-templated-var'; |
582 | } |
583 | $info[] = $context->msg( 'api-help-param-templated' ) |
584 | ->numParams( count( $vars ) ) |
585 | ->params( Message::listParam( $vars ) ) |
586 | ->parse(); |
587 | } |
588 | |
589 | // Type documentation |
590 | foreach ( $paramHelp as $m ) { |
591 | $m->setContext( $context ); |
592 | $info[] = $m->parse(); |
593 | } |
594 | |
595 | foreach ( $info as $i ) { |
596 | $help['parameters'] .= Html::rawElement( 'dd', [ 'class' => 'info' ], $i ); |
597 | } |
598 | } |
599 | |
600 | if ( $dynamicParams !== null ) { |
601 | $dynamicParams = ApiBase::makeMessage( $dynamicParams, $context, [ |
602 | $module->getModulePrefix(), |
603 | $module->getModuleName(), |
604 | $module->getModulePath() |
605 | ] ); |
606 | $help['parameters'] .= Html::element( 'dt', [], '*' ); |
607 | $help['parameters'] .= Html::rawElement( 'dd', |
608 | [ 'class' => 'description' ], $dynamicParams->parse() ); |
609 | } |
610 | |
611 | $help['parameters'] .= Html::closeElement( 'dl' ); |
612 | $help['parameters'] .= Html::closeElement( 'div' ); |
613 | } |
614 | |
615 | $examples = $module->getExamplesMessages(); |
616 | if ( $examples ) { |
617 | $help['examples'] .= Html::openElement( 'div', |
618 | [ 'class' => [ 'apihelp-block', 'apihelp-examples' ] ] ); |
619 | $msg = $context->msg( 'api-help-examples' ); |
620 | if ( !$msg->isDisabled() ) { |
621 | $help['examples'] .= self::wrap( |
622 | $msg->numParams( count( $examples ) ), 'apihelp-block-head', 'div' |
623 | ); |
624 | } |
625 | |
626 | $help['examples'] .= Html::openElement( 'dl' ); |
627 | foreach ( $examples as $qs => $msg ) { |
628 | $msg = ApiBase::makeMessage( $msg, $context, [ |
629 | $module->getModulePrefix(), |
630 | $module->getModuleName(), |
631 | $module->getModulePath() |
632 | ] ); |
633 | |
634 | $link = wfAppendQuery( wfScript( 'api' ), $qs ); |
635 | $sandbox = SpecialPage::getTitleFor( 'ApiSandbox' )->getLocalURL() . '#' . $qs; |
636 | $help['examples'] .= Html::rawElement( 'dt', [], $msg->parse() ); |
637 | $help['examples'] .= Html::rawElement( 'dd', [], |
638 | Html::element( 'a', [ |
639 | 'href' => $link, |
640 | 'dir' => 'ltr', |
641 | 'rel' => 'nofollow', |
642 | ], "api.php?$qs" ) . ' ' . |
643 | Html::rawElement( 'a', [ 'href' => $sandbox ], |
644 | $context->msg( 'api-help-open-in-apisandbox' )->parse() ) |
645 | ); |
646 | } |
647 | $help['examples'] .= Html::closeElement( 'dl' ); |
648 | $help['examples'] .= Html::closeElement( 'div' ); |
649 | } |
650 | |
651 | $subtocnumber = $tocnumber; |
652 | $subtocnumber[$level + 1] = 0; |
653 | $suboptions = [ |
654 | 'submodules' => $options['recursivesubmodules'], |
655 | 'headerlevel' => $level + 1, |
656 | 'tocnumber' => &$subtocnumber, |
657 | 'noheader' => false, |
658 | ] + $options; |
659 | |
660 | // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive |
661 | if ( $options['submodules'] && $module->getModuleManager() ) { |
662 | $manager = $module->getModuleManager(); |
663 | $submodules = []; |
664 | foreach ( $groups as $group ) { |
665 | $names = $manager->getNames( $group ); |
666 | sort( $names ); |
667 | foreach ( $names as $name ) { |
668 | $submodules[] = $manager->getModule( $name ); |
669 | } |
670 | } |
671 | $help['submodules'] .= self::getHelpInternal( |
672 | $context, |
673 | $submodules, |
674 | $suboptions, |
675 | $haveModules |
676 | ); |
677 | } |
678 | |
679 | $module->modifyHelp( $help, $suboptions, $haveModules ); |
680 | |
681 | $module->getHookRunner()->onAPIHelpModifyOutput( $module, $help, |
682 | $suboptions, $haveModules ); |
683 | |
684 | $out .= implode( "\n", $help ); |
685 | } |
686 | |
687 | return $out; |
688 | } |
689 | |
690 | public function shouldCheckMaxlag() { |
691 | return false; |
692 | } |
693 | |
694 | public function isReadMode() { |
695 | return false; |
696 | } |
697 | |
698 | public function getCustomPrinter() { |
699 | $params = $this->extractRequestParams(); |
700 | if ( $params['wrap'] ) { |
701 | return null; |
702 | } |
703 | |
704 | $main = $this->getMain(); |
705 | $errorPrinter = $main->createPrinterByName( $main->getParameter( 'format' ) ); |
706 | return new ApiFormatRaw( $main, $errorPrinter ); |
707 | } |
708 | |
709 | public function getAllowedParams() { |
710 | return [ |
711 | 'modules' => [ |
712 | ParamValidator::PARAM_DEFAULT => 'main', |
713 | ParamValidator::PARAM_ISMULTI => true, |
714 | ], |
715 | 'submodules' => false, |
716 | 'recursivesubmodules' => false, |
717 | 'wrap' => false, |
718 | 'toc' => false, |
719 | ]; |
720 | } |
721 | |
722 | protected function getExamplesMessages() { |
723 | return [ |
724 | 'action=help' |
725 | => 'apihelp-help-example-main', |
726 | 'action=help&modules=query&submodules=1' |
727 | => 'apihelp-help-example-submodules', |
728 | 'action=help&recursivesubmodules=1' |
729 | => 'apihelp-help-example-recursive', |
730 | 'action=help&modules=help' |
731 | => 'apihelp-help-example-help', |
732 | 'action=help&modules=query+info|query+categorymembers' |
733 | => 'apihelp-help-example-query', |
734 | ]; |
735 | } |
736 | |
737 | public function getHelpUrls() { |
738 | return [ |
739 | 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Main_page', |
740 | 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:FAQ', |
741 | 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Quick_start_guide', |
742 | ]; |
743 | } |
744 | } |