44 parent::__construct( $main, $action );
51 foreach ( $params[
'modules'] as
$path ) {
57 $context->setSkin( $this->skinFactory->makeSkin(
'apioutput' ) );
61 $out->setRobotPolicy(
'noindex,nofollow' );
62 $out->setCopyrightUrl(
'https://www.mediawiki.org/wiki/Special:MyLanguage/Copyright' );
63 $out->disallowUserJs();
64 $context->setOutput( $out );
71 $html = ob_get_clean();
74 if ( $params[
'wrap'] ) {
76 'mime' =>
'text/html',
77 'filename' =>
'api-help.html',
85 'Types' => [
'AssocAsObject' => true ],
88 $errors = array_filter( [
89 'errors' => $this->
getResult()->getResultData( [
'errors' ], $transform ),
90 'warnings' => $this->
getResult()->getResultData( [
'warnings' ], $transform ),
93 $json = FormatJson::encode( $errors,
true, FormatJson::UTF8_OK );
96 $json = str_replace(
'--',
'-\u002D', $json );
97 $html =
"<!-- API warnings and errors:\n$json\n-->\n$html";
127 if ( !is_array( $modules ) ) {
128 $modules = [ $modules ];
132 $out->addModuleStyles( [
134 'mediawiki.apipretty',
136 $out->setPageTitleMsg( $context->
msg(
'api-help-title' ) );
139 $cache = $services->getMainWANObjectCache();
141 if ( count( $modules ) == 1 && $modules[0] instanceof
ApiMain &&
142 $options[
'recursivesubmodules'] &&
143 $context->
getLanguage()->equals( $services->getContentLanguage() )
146 if ( $cacheHelpTimeout > 0 ) {
148 $cacheKey = $cache->makeKey(
'apihelp', $modules[0]->
getModulePath(),
149 (
int)!empty( $options[
'toc'] ),
150 str_replace(
' ',
'_', SpecialVersion::getVersion(
'nodb' ) ) );
151 $cached = $cache->get( $cacheKey );
153 $out->addHTML( $cached );
158 if ( $out->getHTML() !==
'' ) {
167 $options[
'toc'] =
true;
169 $options[
'recursivesubmodules'] = !empty( $options[
'recursivesubmodules'] );
170 $options[
'submodules'] = $options[
'recursivesubmodules'] || !empty( $options[
'submodules'] );
172 $html = self::getHelpInternal( $context, $modules, $options, $haveModules );
174 if ( !empty( $options[
'toc'] ) && $haveModules ) {
175 $out->addTOCPlaceholder(
new TOCData( ...array_values( $haveModules ) ) );
179 if ( empty( $options[
'nolead'] ) ) {
180 $msg = $context->
msg(
'api-help-lead' );
181 if ( !$msg->isDisabled() ) {
182 $out->addHTML( $msg->parseAsBlock() );
186 $out->addHTML( $html );
188 $helptitle = $options[
'helptitle'] ??
null;
191 $out->addHTML( $html );
193 if ( $cacheKey !==
null ) {
195 $cache->set( $cacheKey, $out->getHTML(), $cacheHelpTimeout );
207 public static function fixHelpLinks( $html, $helptitle =
null, $localModules = [] ) {
210 static function ( SerializerNode $node ):
bool {
211 return $node->name ===
'a'
212 && isset( $node->attrs[
'href'] )
213 && !str_contains( $node->attrs[
'class'] ??
'',
'apihelp-linktrail' );
215 static function ( SerializerNode $node ) use ( $helptitle, $localModules ): SerializerNode {
216 $href = $node->attrs[
'href'];
220 $href = rawurldecode( $href );
221 }
while ( $old !== $href );
222 if ( preg_match(
'!Special:ApiHelp/([^&/|#]+)((?:#.*)?)!', $href, $m ) ) {
223 if ( isset( $localModules[$m[1]] ) ) {
224 $href = $m[2] ===
'' ?
'#' . $m[1] : $m[2];
225 } elseif ( $helptitle !==
null ) {
226 $href = Title::newFromText( str_replace(
'$1', $m[1], $helptitle ) . $m[2] )
234 $node->attrs[
'href'] = $href;
235 unset( $node->attrs[
'title'] );
251 private static function wrap(
Message $msg, $class, $tag =
'span' ) {
252 return Html::rawElement( $tag, [
'class' => $class ],
266 private static function getHelpInternal(
IContextSource $context, array $modules,
267 array $options, &$haveModules
271 $level = empty( $options[
'headerlevel'] ) ? 2 : $options[
'headerlevel'];
272 if ( empty( $options[
'tocnumber'] ) ) {
273 $tocnumber = [ 2 => 0 ];
275 $tocnumber = &$options[
'tocnumber'];
278 foreach ( $modules as $module ) {
279 $paramValidator = $module->getMain()->getParamValidator();
280 $tocnumber[$level]++;
281 $path = $module->getModulePath();
282 $module->setContext( $context );
293 if ( empty( $options[
'noheader'] ) || !empty( $options[
'toc'] ) ) {
296 while ( isset( $haveModules[$anchor] ) ) {
297 $anchor =
$path .
'|' . ++$i;
300 if ( $module->isMain() ) {
301 $headerContent = $context->
msg(
'api-help-main-header' )->parse();
303 'class' =>
'apihelp-header',
306 $name = $module->getModuleName();
307 $headerContent = htmlspecialchars(
308 $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
"=$name"
310 if ( $module->getModulePrefix() !==
'' ) {
311 $headerContent .=
' ' .
312 $context->
msg(
'parentheses', $module->getModulePrefix() )->parse();
318 'class' => [
'apihelp-header',
'apihelp-module-name' ],
324 $headerAttr[
'id'] = $anchor;
326 $haveModules[$anchor] =
new SectionMetadata(
327 tocLevel: count( $tocnumber ),
329 line: $headerContent,
330 number: implode(
'.', $tocnumber ),
331 index: (string)( 1 + count( $haveModules ) ),
333 linkAnchor: Sanitizer::escapeIdForLink( $anchor ),
335 if ( empty( $options[
'noheader'] ) ) {
336 $help[
'header'] .= Html::rawElement(
337 'h' . min( 6, $level ),
343 $haveModules[
$path] =
true;
348 for ( $m = $module; $m !==
null; $m = $m->getParent() ) {
349 $name = $m->getModuleName();
350 if ( $name ===
'main_int' ) {
354 if ( count( $modules ) === 1 && $m === $modules[0] &&
355 !( !empty( $options[
'submodules'] ) && $m->getModuleManager() )
357 $link =
Html::element(
'b', [
'dir' =>
'ltr',
'lang' =>
'en' ], $name );
361 [
'href' => $link,
'class' =>
'apihelp-linktrail',
'dir' =>
'ltr',
'lang' =>
'en' ],
366 array_unshift( $links, $link );
369 $help[
'header'] .= self::wrap(
370 $context->
msg(
'parentheses' )
371 ->rawParams( $context->
getLanguage()->pipeList( $links ) ),
372 'apihelp-linktrail',
'div'
376 $flags = $module->getHelpFlags();
377 $help[
'flags'] .= Html::openElement(
'div',
378 [
'class' => [
'apihelp-block',
'apihelp-flags' ] ] );
379 $msg = $context->
msg(
'api-help-flags' );
380 if ( !$msg->isDisabled() ) {
381 $help[
'flags'] .= self::wrap(
382 $msg->numParams( count( $flags ) ),
'apihelp-block-head',
'div'
385 $help[
'flags'] .= Html::openElement(
'ul' );
386 foreach ( $flags as $flag ) {
387 $help[
'flags'] .= Html::rawElement(
'li', [],
394 self::wrap( $context->
msg(
"api-help-flag-$flag" ),
"apihelp-flag-$flag" )
397 $sourceInfo = $module->getModuleSourceInfo();
399 if ( isset( $sourceInfo[
'namemsg'] ) ) {
400 $extname = $context->
msg( $sourceInfo[
'namemsg'] )->text();
403 $extname =
Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'name'] );
405 $help[
'flags'] .= Html::rawElement(
'li', [],
407 $context->
msg(
'api-help-source', $extname, $sourceInfo[
'name'] ),
414 if ( isset( $sourceInfo[
'license-name'] ) ) {
415 $msg = $context->
msg(
'api-help-license', $linkText,
416 Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'license-name'] )
418 } elseif ( ExtensionInfo::getLicenseFileNames( dirname( $sourceInfo[
'path'] ) ) ) {
419 $msg = $context->
msg(
'api-help-license-noname', $linkText );
421 $msg = $context->
msg(
'api-help-license-unknown' );
423 $help[
'flags'] .= Html::rawElement(
'li', [],
424 self::wrap( $msg,
'apihelp-license' )
427 $help[
'flags'] .= Html::rawElement(
'li', [],
428 self::wrap( $context->
msg(
'api-help-source-unknown' ),
'apihelp-source' )
430 $help[
'flags'] .= Html::rawElement(
'li', [],
431 self::wrap( $context->
msg(
'api-help-license-unknown' ),
'apihelp-license' )
434 $help[
'flags'] .= Html::closeElement(
'ul' );
435 $help[
'flags'] .= Html::closeElement(
'div' );
437 foreach ( $module->getFinalDescription() as $msg ) {
438 $msg->setContext( $context );
439 $help[
'description'] .= $msg->parseAsBlock();
442 $urls = $module->getHelpUrls();
444 if ( !is_array( $urls ) ) {
447 $help[
'help-urls'] .= Html::openElement(
'div',
448 [
'class' => [
'apihelp-block',
'apihelp-help-urls' ] ]
450 $msg = $context->
msg(
'api-help-help-urls' );
451 if ( !$msg->isDisabled() ) {
452 $help[
'help-urls'] .= self::wrap(
453 $msg->numParams( count( $urls ) ),
'apihelp-block-head',
'div'
456 $help[
'help-urls'] .= Html::openElement(
'ul' );
457 foreach ( $urls as
$url ) {
458 $help[
'help-urls'] .= Html::rawElement(
'li', [],
462 $help[
'help-urls'] .= Html::closeElement(
'ul' );
463 $help[
'help-urls'] .= Html::closeElement(
'div' );
467 $dynamicParams = $module->dynamicParameterDocumentation();
469 if ( $params || $dynamicParams !==
null ) {
470 $help[
'parameters'] .= Html::openElement(
'div',
471 [
'class' => [
'apihelp-block',
'apihelp-parameters' ] ]
473 $msg = $context->
msg(
'api-help-parameters' );
474 if ( !$msg->isDisabled() ) {
475 $help[
'parameters'] .= self::wrap(
476 $msg->numParams( count( $params ) ),
'apihelp-block-head',
'div'
478 if ( !$module->isMain() ) {
480 $help[
'parameters'] .= self::wrap(
481 $context->
msg(
'api-help-parameters-note' ),
'apihelp-block-header',
'div'
485 $help[
'parameters'] .= Html::openElement(
'dl' );
487 $descriptions = $module->getFinalParamDescription();
489 foreach ( $params as $name => $settings ) {
490 $settings = $paramValidator->normalizeSettings( $settings );
492 if ( $settings[ParamValidator::PARAM_TYPE] ===
'submodule' ) {
496 $encodedParamName = $module->encodeParamName( $name );
497 $paramNameAttribs = [
'dir' =>
'ltr',
'lang' =>
'en' ];
498 if ( isset( $anchor ) ) {
499 $paramNameAttribs[
'id'] =
"$anchor:$encodedParamName";
501 $help[
'parameters'] .= Html::rawElement(
'dt', [],
502 Html::element(
'span', $paramNameAttribs, $encodedParamName )
507 if ( isset( $descriptions[$name] ) ) {
508 foreach ( $descriptions[$name] as $msg ) {
509 $msg->setContext( $context );
510 $description[] = $msg->parseAsBlock();
513 if ( !array_filter( $description ) ) {
514 $description = [ self::wrap(
515 $context->
msg(
'api-help-param-no-description' ),
521 if ( !empty( $settings[ParamValidator::PARAM_DEPRECATED] ) ) {
522 $help[
'parameters'] .= Html::openElement(
'dd',
523 [
'class' =>
'info' ] );
524 $help[
'parameters'] .= self::wrap(
525 $context->
msg(
'api-help-param-deprecated' ),
526 'apihelp-deprecated',
'strong'
528 $help[
'parameters'] .= Html::closeElement(
'dd' );
531 if ( $description ) {
532 $description = implode(
'', $description );
533 $description = preg_replace(
'!\s*</([oud]l)>\s*<\1>\s*!',
"\n", $description );
534 $help[
'parameters'] .= Html::rawElement(
'dd',
535 [
'class' =>
'description' ], $description );
540 $paramHelp = $paramValidator->getHelpInfo( $module, $name, $settings, [] );
542 unset( $paramHelp[ParamValidator::PARAM_DEPRECATED] );
544 if ( isset( $paramHelp[ParamValidator::PARAM_REQUIRED] ) ) {
545 $paramHelp[ParamValidator::PARAM_REQUIRED]->setContext( $context );
546 $info[] = $paramHelp[ParamValidator::PARAM_REQUIRED];
547 unset( $paramHelp[ParamValidator::PARAM_REQUIRED] );
553 $tag = array_shift( $i );
554 $info[] = $context->
msg(
"apihelp-{$path}-paraminfo-{$tag}" )
555 ->numParams( count( $i ) )
556 ->params( $context->
getLanguage()->commaList( $i ) )
557 ->params( $module->getModulePrefix() )
565 $msg =
'api-help-param-templated-var-first';
567 $vars[] = $context->
msg( $msg, $k, $module->encodeParamName( $v ) );
568 $msg =
'api-help-param-templated-var';
570 $info[] = $context->
msg(
'api-help-param-templated' )
571 ->numParams( count( $vars ) )
577 foreach ( $paramHelp as $m ) {
578 $m->setContext( $context );
579 $info[] = $m->parse();
582 foreach ( $info as $i ) {
583 $help[
'parameters'] .= Html::rawElement(
'dd', [
'class' =>
'info' ], $i );
587 if ( $dynamicParams !==
null ) {
588 $dynamicParams = $context->
msg(
590 $module->getModulePrefix(),
591 $module->getModuleName(),
592 $module->getModulePath()
595 $help[
'parameters'] .= Html::rawElement(
'dd',
596 [
'class' =>
'description' ], $dynamicParams->parse() );
599 $help[
'parameters'] .= Html::closeElement(
'dl' );
600 $help[
'parameters'] .= Html::closeElement(
'div' );
603 $examples = $module->getExamplesMessages();
605 $help[
'examples'] .= Html::openElement(
'div',
606 [
'class' => [
'apihelp-block',
'apihelp-examples' ] ] );
607 $msg = $context->
msg(
'api-help-examples' );
608 if ( !$msg->isDisabled() ) {
609 $help[
'examples'] .= self::wrap(
610 $msg->numParams( count( $examples ) ),
'apihelp-block-head',
'div'
614 $help[
'examples'] .= Html::openElement(
'dl' );
615 foreach ( $examples as $qs => $msg ) {
616 $msg = $context->
msg(
618 $module->getModulePrefix(),
619 $module->getModuleName(),
620 $module->getModulePath()
625 $help[
'examples'] .= Html::rawElement(
'dt', [], $msg->parse() );
626 $help[
'examples'] .= Html::rawElement(
'dd', [],
631 ],
"api.php?$qs" ) .
' ' .
632 Html::rawElement(
'a', [
'href' => $sandbox ],
633 $context->
msg(
'api-help-open-in-apisandbox' )->parse() )
636 $help[
'examples'] .= Html::closeElement(
'dl' );
637 $help[
'examples'] .= Html::closeElement(
'div' );
640 $subtocnumber = $tocnumber;
641 $subtocnumber[$level + 1] = 0;
643 'submodules' => $options[
'recursivesubmodules'],
644 'headerlevel' => $level + 1,
645 'tocnumber' => &$subtocnumber,
649 if ( $options[
'submodules'] && $module->getModuleManager() ) {
650 $manager = $module->getModuleManager();
652 foreach ( $groups as $group ) {
653 $names = $manager->getNames( $group );
655 foreach ( $names as $name ) {
656 $submodules[] = $manager->getModule( $name );
659 $help[
'submodules'] .= self::getHelpInternal(
667 $module->modifyHelp( $help, $suboptions, $haveModules );
669 if ( $module->getHookContainer()->isRegistered(
'APIHelpModifyOutput' ) ) {
672 if ( !empty( $suboptions[
'toc'] ) ) {
673 $haveModules = array_map(
674 static fn ( $s )=>$s->toLegacy(), $haveModules
677 $module->getHookRunner()->onAPIHelpModifyOutput(
678 $module, $help, $suboptions, $haveModules
680 if ( !empty( $suboptions[
'toc'] ) ) {
681 $haveModules = array_map(
682 static fn ( $s )=>SectionMetadata::fromLegacy( $s ), $haveModules
687 $out .= implode(
"\n", $help );
706 if ( $params[
'wrap'] ) {
711 $errorPrinter = $main->createPrinterByName( $main->getParameter(
'format' ) );
719 ParamValidator::PARAM_DEFAULT =>
'main',
720 ParamValidator::PARAM_ISMULTI =>
true,
722 'submodules' =>
false,
723 'recursivesubmodules' =>
false,
733 =>
'apihelp-help-example-main',
734 'action=help&modules=query&submodules=1'
735 =>
'apihelp-help-example-submodules',
736 'action=help&recursivesubmodules=1&toc'
737 =>
'apihelp-help-example-recursive',
738 'action=help&modules=help'
739 =>
'apihelp-help-example-help',
740 'action=help&modules=query+info|query+categorymembers'
741 =>
'apihelp-help-example-query',
748 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Main_page',
749 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:FAQ',
750 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Quick_start_guide',