46 parent::__construct( $main, $action );
53 foreach ( $params[
'modules'] as
$path ) {
59 $context->setSkin( $this->skinFactory->makeSkin(
'apioutput' ) );
63 $out->setRobotPolicy(
'noindex,nofollow' );
64 $out->setCopyrightUrl(
'https://www.mediawiki.org/wiki/Special:MyLanguage/Copyright' );
65 $out->disallowUserJs();
66 $out->reduceAllowedModules(
RL\Module::TYPE_SCRIPTS,
RL\Module::ORIGIN_NONE );
67 $context->setOutput( $out );
74 $html = ob_get_clean();
77 if ( $params[
'wrap'] ) {
79 'mime' =>
'text/html',
80 'filename' =>
'api-help.html',
88 'Types' => [
'AssocAsObject' => true ],
91 $errors = array_filter( [
92 'errors' => $this->
getResult()->getResultData( [
'errors' ], $transform ),
93 'warnings' => $this->
getResult()->getResultData( [
'warnings' ], $transform ),
96 $json = FormatJson::encode( $errors,
true, FormatJson::UTF8_OK );
99 $json = str_replace(
'--',
'-\u002D', $json );
100 $html =
"<!-- API warnings and errors:\n$json\n-->\n$html";
130 if ( !is_array( $modules ) ) {
131 $modules = [ $modules ];
135 $out->addModuleStyles( [
137 'mediawiki.apipretty',
139 $out->setPageTitleMsg( $context->
msg(
'api-help-title' ) );
142 $cache = $services->getMainWANObjectCache();
144 if ( count( $modules ) == 1 && $modules[0] instanceof
ApiMain &&
145 $options[
'recursivesubmodules'] &&
146 $context->
getLanguage()->equals( $services->getContentLanguage() )
149 if ( $cacheHelpTimeout > 0 ) {
151 $cacheKey = $cache->makeKey(
'apihelp', $modules[0]->
getModulePath(),
152 (
int)!empty( $options[
'toc'] ),
153 str_replace(
' ',
'_', SpecialVersion::getVersion(
'nodb' ) ) );
154 $cached = $cache->get( $cacheKey );
156 $out->addHTML( $cached );
161 if ( $out->getHTML() !==
'' ) {
170 $options[
'toc'] =
true;
172 $options[
'recursivesubmodules'] = !empty( $options[
'recursivesubmodules'] );
173 $options[
'submodules'] = $options[
'recursivesubmodules'] || !empty( $options[
'submodules'] );
175 $html = self::getHelpInternal( $context, $modules, $options, $haveModules );
177 if ( !empty( $options[
'toc'] ) && $haveModules ) {
178 $out->addTOCPlaceholder(
new TOCData( ...array_values( $haveModules ) ) );
182 if ( empty( $options[
'nolead'] ) ) {
183 $msg = $context->
msg(
'api-help-lead' );
184 if ( !$msg->isDisabled() ) {
185 $out->addHTML( $msg->parseAsBlock() );
189 $out->addHTML( $html );
191 $helptitle = $options[
'helptitle'] ??
null;
194 $out->addHTML( $html );
196 if ( $cacheKey !==
null ) {
198 $cache->set( $cacheKey, $out->getHTML(), $cacheHelpTimeout );
210 public static function fixHelpLinks( $html, $helptitle =
null, $localModules = [] ) {
213 static function ( SerializerNode $node ):
bool {
214 return $node->name ===
'a'
215 && isset( $node->attrs[
'href'] )
216 && !str_contains( $node->attrs[
'class'] ??
'',
'apihelp-linktrail' );
218 static function ( SerializerNode $node ) use ( $helptitle, $localModules ): SerializerNode {
219 $href = $node->attrs[
'href'];
223 $href = rawurldecode( $href );
224 }
while ( $old !== $href );
225 if ( preg_match(
'!Special:ApiHelp/([^&/|#]+)((?:#.*)?)!', $href, $m ) ) {
226 if ( isset( $localModules[$m[1]] ) ) {
227 $href = $m[2] ===
'' ?
'#' . $m[1] : $m[2];
228 } elseif ( $helptitle !==
null ) {
229 $href = Title::newFromText( str_replace(
'$1', $m[1], $helptitle ) . $m[2] )
237 $node->attrs[
'href'] = $href;
238 unset( $node->attrs[
'title'] );
254 private static function wrap(
Message $msg, $class, $tag =
'span' ) {
255 return Html::rawElement( $tag, [
'class' => $class ],
269 private static function getHelpInternal(
IContextSource $context, array $modules,
270 array $options, &$haveModules
274 $level = empty( $options[
'headerlevel'] ) ? 2 : $options[
'headerlevel'];
275 if ( empty( $options[
'tocnumber'] ) ) {
276 $tocnumber = [ 2 => 0 ];
278 $tocnumber = &$options[
'tocnumber'];
281 foreach ( $modules as $module ) {
282 $paramValidator = $module->getMain()->getParamValidator();
283 $tocnumber[$level]++;
284 $path = $module->getModulePath();
285 $module->setContext( $context );
296 if ( empty( $options[
'noheader'] ) || !empty( $options[
'toc'] ) ) {
299 while ( isset( $haveModules[$anchor] ) ) {
300 $anchor =
$path .
'|' . ++$i;
303 if ( $module->isMain() ) {
304 $headerContent = $context->
msg(
'api-help-main-header' )->parse();
306 'class' =>
'apihelp-header',
309 $name = $module->getModuleName();
310 $headerContent = htmlspecialchars(
311 $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
"=$name"
313 if ( $module->getModulePrefix() !==
'' ) {
314 $headerContent .=
' ' .
315 $context->
msg(
'parentheses', $module->getModulePrefix() )->parse();
321 'class' => [
'apihelp-header',
'apihelp-module-name' ],
327 $headerAttr[
'id'] = $anchor;
329 $haveModules[$anchor] =
new SectionMetadata(
330 tocLevel: count( $tocnumber ),
332 line: $headerContent,
333 number: implode(
'.', $tocnumber ),
334 index: (string)( 1 + count( $haveModules ) ),
336 linkAnchor: Sanitizer::escapeIdForLink( $anchor ),
338 if ( empty( $options[
'noheader'] ) ) {
339 $help[
'header'] .= Html::rawElement(
340 'h' . min( 6, $level ),
346 $haveModules[
$path] =
true;
351 for ( $m = $module; $m !==
null; $m = $m->getParent() ) {
352 $name = $m->getModuleName();
353 if ( $name ===
'main_int' ) {
357 if ( count( $modules ) === 1 && $m === $modules[0] &&
358 !( !empty( $options[
'submodules'] ) && $m->getModuleManager() )
360 $link =
Html::element(
'b', [
'dir' =>
'ltr',
'lang' =>
'en' ], $name );
364 [
'href' => $link,
'class' =>
'apihelp-linktrail',
'dir' =>
'ltr',
'lang' =>
'en' ],
369 array_unshift( $links, $link );
372 $help[
'header'] .= self::wrap(
373 $context->
msg(
'parentheses' )
374 ->rawParams( $context->
getLanguage()->pipeList( $links ) ),
375 'apihelp-linktrail',
'div'
379 $flags = $module->getHelpFlags();
380 $help[
'flags'] .= Html::openElement(
'div',
381 [
'class' => [
'apihelp-block',
'apihelp-flags' ] ] );
382 $msg = $context->
msg(
'api-help-flags' );
383 if ( !$msg->isDisabled() ) {
384 $help[
'flags'] .= self::wrap(
385 $msg->numParams( count( $flags ) ),
'apihelp-block-head',
'div'
388 $help[
'flags'] .= Html::openElement(
'ul' );
389 foreach ( $flags as $flag ) {
390 $help[
'flags'] .= Html::rawElement(
'li', [],
397 self::wrap( $context->
msg(
"api-help-flag-$flag" ),
"apihelp-flag-$flag" )
400 $sourceInfo = $module->getModuleSourceInfo();
402 if ( isset( $sourceInfo[
'namemsg'] ) ) {
403 $extname = $context->
msg( $sourceInfo[
'namemsg'] )->text();
406 $extname =
Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'name'] );
408 $help[
'flags'] .= Html::rawElement(
'li', [],
410 $context->
msg(
'api-help-source', $extname, $sourceInfo[
'name'] ),
417 if ( isset( $sourceInfo[
'license-name'] ) ) {
418 $msg = $context->
msg(
'api-help-license', $linkText,
419 Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'license-name'] )
421 } elseif ( ExtensionInfo::getLicenseFileNames( dirname( $sourceInfo[
'path'] ) ) ) {
422 $msg = $context->
msg(
'api-help-license-noname', $linkText );
424 $msg = $context->
msg(
'api-help-license-unknown' );
426 $help[
'flags'] .= Html::rawElement(
'li', [],
427 self::wrap( $msg,
'apihelp-license' )
430 $help[
'flags'] .= Html::rawElement(
'li', [],
431 self::wrap( $context->
msg(
'api-help-source-unknown' ),
'apihelp-source' )
433 $help[
'flags'] .= Html::rawElement(
'li', [],
434 self::wrap( $context->
msg(
'api-help-license-unknown' ),
'apihelp-license' )
437 $help[
'flags'] .= Html::closeElement(
'ul' );
438 $help[
'flags'] .= Html::closeElement(
'div' );
440 foreach ( $module->getFinalDescription() as $msg ) {
441 $msg->setContext( $context );
442 $help[
'description'] .= $msg->parseAsBlock();
445 $urls = $module->getHelpUrls();
447 if ( !is_array( $urls ) ) {
450 $help[
'help-urls'] .= Html::openElement(
'div',
451 [
'class' => [
'apihelp-block',
'apihelp-help-urls' ] ]
453 $msg = $context->
msg(
'api-help-help-urls' );
454 if ( !$msg->isDisabled() ) {
455 $help[
'help-urls'] .= self::wrap(
456 $msg->numParams( count( $urls ) ),
'apihelp-block-head',
'div'
459 $help[
'help-urls'] .= Html::openElement(
'ul' );
460 foreach ( $urls as
$url ) {
461 $help[
'help-urls'] .= Html::rawElement(
'li', [],
465 $help[
'help-urls'] .= Html::closeElement(
'ul' );
466 $help[
'help-urls'] .= Html::closeElement(
'div' );
470 $dynamicParams = $module->dynamicParameterDocumentation();
472 if ( $params || $dynamicParams !==
null ) {
473 $help[
'parameters'] .= Html::openElement(
'div',
474 [
'class' => [
'apihelp-block',
'apihelp-parameters' ] ]
476 $msg = $context->
msg(
'api-help-parameters' );
477 if ( !$msg->isDisabled() ) {
478 $help[
'parameters'] .= self::wrap(
479 $msg->numParams( count( $params ) ),
'apihelp-block-head',
'div'
481 if ( !$module->isMain() ) {
483 $help[
'parameters'] .= self::wrap(
484 $context->
msg(
'api-help-parameters-note' ),
'apihelp-block-header',
'div'
488 $help[
'parameters'] .= Html::openElement(
'dl' );
490 $descriptions = $module->getFinalParamDescription();
492 foreach ( $params as $name => $settings ) {
493 $settings = $paramValidator->normalizeSettings( $settings );
495 if ( $settings[ParamValidator::PARAM_TYPE] ===
'submodule' ) {
499 $encodedParamName = $module->encodeParamName( $name );
500 $paramNameAttribs = [
'dir' =>
'ltr',
'lang' =>
'en' ];
501 if ( isset( $anchor ) ) {
502 $paramNameAttribs[
'id'] =
"$anchor:$encodedParamName";
504 $help[
'parameters'] .= Html::rawElement(
'dt', [],
505 Html::element(
'span', $paramNameAttribs, $encodedParamName )
510 if ( isset( $descriptions[$name] ) ) {
511 foreach ( $descriptions[$name] as $msg ) {
512 $msg->setContext( $context );
513 $description[] = $msg->parseAsBlock();
516 if ( !array_filter( $description ) ) {
517 $description = [ self::wrap(
518 $context->
msg(
'api-help-param-no-description' ),
524 if ( !empty( $settings[ParamValidator::PARAM_DEPRECATED] ) ) {
525 $help[
'parameters'] .= Html::openElement(
'dd',
526 [
'class' =>
'info' ] );
527 $help[
'parameters'] .= self::wrap(
528 $context->
msg(
'api-help-param-deprecated' ),
529 'apihelp-deprecated',
'strong'
531 $help[
'parameters'] .= Html::closeElement(
'dd' );
534 if ( $description ) {
535 $description = implode(
'', $description );
536 $description = preg_replace(
'!\s*</([oud]l)>\s*<\1>\s*!',
"\n", $description );
537 $help[
'parameters'] .= Html::rawElement(
'dd',
538 [
'class' =>
'description' ], $description );
543 $paramHelp = $paramValidator->getHelpInfo( $module, $name, $settings, [] );
545 unset( $paramHelp[ParamValidator::PARAM_DEPRECATED] );
547 if ( isset( $paramHelp[ParamValidator::PARAM_REQUIRED] ) ) {
548 $paramHelp[ParamValidator::PARAM_REQUIRED]->setContext( $context );
549 $info[] = $paramHelp[ParamValidator::PARAM_REQUIRED];
550 unset( $paramHelp[ParamValidator::PARAM_REQUIRED] );
556 $tag = array_shift( $i );
557 $info[] = $context->
msg(
"apihelp-{$path}-paraminfo-{$tag}" )
558 ->numParams( count( $i ) )
559 ->params( $context->
getLanguage()->commaList( $i ) )
560 ->params( $module->getModulePrefix() )
568 $msg =
'api-help-param-templated-var-first';
570 $vars[] = $context->
msg( $msg, $k, $module->encodeParamName( $v ) );
571 $msg =
'api-help-param-templated-var';
573 $info[] = $context->
msg(
'api-help-param-templated' )
574 ->numParams( count( $vars ) )
580 foreach ( $paramHelp as $m ) {
581 $m->setContext( $context );
582 $info[] = $m->parse();
585 foreach ( $info as $i ) {
586 $help[
'parameters'] .= Html::rawElement(
'dd', [
'class' =>
'info' ], $i );
590 if ( $dynamicParams !==
null ) {
591 $dynamicParams = $context->
msg(
593 $module->getModulePrefix(),
594 $module->getModuleName(),
595 $module->getModulePath()
598 $help[
'parameters'] .= Html::rawElement(
'dd',
599 [
'class' =>
'description' ], $dynamicParams->parse() );
602 $help[
'parameters'] .= Html::closeElement(
'dl' );
603 $help[
'parameters'] .= Html::closeElement(
'div' );
606 $examples = $module->getExamplesMessages();
608 $help[
'examples'] .= Html::openElement(
'div',
609 [
'class' => [
'apihelp-block',
'apihelp-examples' ] ] );
610 $msg = $context->
msg(
'api-help-examples' );
611 if ( !$msg->isDisabled() ) {
612 $help[
'examples'] .= self::wrap(
613 $msg->numParams( count( $examples ) ),
'apihelp-block-head',
'div'
617 $help[
'examples'] .= Html::openElement(
'dl' );
618 foreach ( $examples as $qs => $msg ) {
619 $msg = $context->
msg(
621 $module->getModulePrefix(),
622 $module->getModuleName(),
623 $module->getModulePath()
628 $help[
'examples'] .= Html::rawElement(
'dt', [], $msg->parse() );
629 $help[
'examples'] .= Html::rawElement(
'dd', [],
634 ],
"api.php?$qs" ) .
' ' .
635 Html::rawElement(
'a', [
'href' => $sandbox ],
636 $context->
msg(
'api-help-open-in-apisandbox' )->parse() )
639 $help[
'examples'] .= Html::closeElement(
'dl' );
640 $help[
'examples'] .= Html::closeElement(
'div' );
643 $subtocnumber = $tocnumber;
644 $subtocnumber[$level + 1] = 0;
646 'submodules' => $options[
'recursivesubmodules'],
647 'headerlevel' => $level + 1,
648 'tocnumber' => &$subtocnumber,
652 if ( $options[
'submodules'] && $module->getModuleManager() ) {
653 $manager = $module->getModuleManager();
655 foreach ( $groups as $group ) {
656 $names = $manager->getNames( $group );
658 foreach ( $names as $name ) {
659 $submodules[] = $manager->getModule( $name );
662 $help[
'submodules'] .= self::getHelpInternal(
671 $module instanceof ApiMain ||
672 MWDebug::detectDeprecatedOverride( $module, ApiBase::class,
'modifyHelp',
'1.47' )
674 $module->modifyHelp( $help, $suboptions, $haveModules );
677 if ( $module->getHookContainer()->isRegistered(
'APIHelpModifyOutput' ) ) {
679 if ( !empty( $suboptions[
'toc'] ) ) {
680 $haveModules = array_map(
681 static fn ( $s )=>$s->toLegacy(), $haveModules
684 $module->getHookRunner()->onAPIHelpModifyOutput(
685 $module, $help, $suboptions, $haveModules
687 if ( !empty( $suboptions[
'toc'] ) ) {
688 $haveModules = array_map(
689 static fn ( $s )=>SectionMetadata::fromLegacy( $s ), $haveModules
694 $out .= implode(
"\n", $help );
713 if ( $params[
'wrap'] ) {
718 $errorPrinter = $main->createPrinterByName( $main->getParameter(
'format' ) );
726 ParamValidator::PARAM_DEFAULT =>
'main',
727 ParamValidator::PARAM_ISMULTI =>
true,
729 'submodules' =>
false,
730 'recursivesubmodules' =>
false,
740 =>
'apihelp-help-example-main',
741 'action=help&modules=query&submodules=1'
742 =>
'apihelp-help-example-submodules',
743 'action=help&recursivesubmodules=1&toc'
744 =>
'apihelp-help-example-recursive',
745 'action=help&modules=help'
746 =>
'apihelp-help-example-help',
747 'action=help&modules=query+info|query+categorymembers'
748 =>
'apihelp-help-example-query',
755 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Main_page',
756 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:FAQ',
757 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Quick_start_guide',