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