62 parent::__construct( $main, $action );
63 $this->skinFactory = $skinFactory;
76 $context->setSkin( $this->skinFactory->makeSkin(
'apioutput' ) );
78 $context->setTitle( SpecialPage::getTitleFor(
'ApiHelp' ) );
80 $out->setRobotPolicy(
'noindex,nofollow' );
81 $out->setCopyrightUrl(
'https://www.mediawiki.org/wiki/Special:MyLanguage/Copyright' );
82 $out->disallowUserJs();
83 $context->setOutput( $out );
85 self::getHelp( $context, $modules,
$params );
90 $html = ob_get_clean();
95 'mime' =>
'text/html',
96 'filename' =>
'api-help.html',
99 ApiResult::setSubelementsList( $data,
'help' );
104 'Types' => [
'AssocAsObject' => true ],
107 $errors = array_filter( [
108 'errors' => $this->
getResult()->getResultData( [
'errors' ], $transform ),
109 'warnings' => $this->
getResult()->getResultData( [
'warnings' ], $transform ),
112 $json = FormatJson::encode( $errors,
true, FormatJson::UTF8_OK );
115 $json = str_replace(
'--',
'-\u002D', $json );
116 $html =
"<!-- API warnings and errors:\n$json\n-->\n$html";
120 $result->addValue(
null,
'text', $html, ApiResult::NO_SIZE_CHECK );
121 $result->addValue(
null,
'mime',
'text/html', ApiResult::NO_SIZE_CHECK );
122 $result->addValue(
null,
'filename',
'api-help.html', ApiResult::NO_SIZE_CHECK );
146 if ( !is_array( $modules ) ) {
147 $modules = [ $modules ];
151 $out->addModuleStyles( [
153 'mediawiki.apipretty',
155 $out->setPageTitleMsg( $context->
msg(
'api-help-title' ) );
157 $services = MediaWikiServices::getInstance();
158 $cache = $services->getMainWANObjectCache();
160 if ( count( $modules ) == 1 && $modules[0] instanceof
ApiMain &&
161 $options[
'recursivesubmodules'] &&
162 $context->
getLanguage()->equals( $services->getContentLanguage() )
164 $cacheHelpTimeout = $context->
getConfig()->get( MainConfigNames::APICacheHelpTimeout );
165 if ( $cacheHelpTimeout > 0 ) {
167 $cacheKey = $cache->makeKey(
'apihelp', $modules[0]->
getModulePath(),
168 (
int)!empty( $options[
'toc'] ),
169 str_replace(
' ',
'_', SpecialVersion::getVersion(
'nodb' ) ) );
170 $cached = $cache->get( $cacheKey );
172 $out->addHTML( $cached );
177 if ( $out->getHTML() !==
'' ) {
183 $options[
'recursivesubmodules'] = !empty( $options[
'recursivesubmodules'] );
184 $options[
'submodules'] = $options[
'recursivesubmodules'] || !empty( $options[
'submodules'] );
187 if ( empty( $options[
'nolead'] ) ) {
188 $msg = $context->
msg(
'api-help-lead' );
189 if ( !$msg->isDisabled() ) {
190 $out->addHTML( $msg->parseAsBlock() );
195 $html = self::getHelpInternal( $context, $modules, $options, $haveModules );
196 if ( !empty( $options[
'toc'] ) && $haveModules ) {
198 $pout->
setTOCData( TOCData::fromLegacy( array_values( $haveModules ) ) );
199 $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
200 $pout->setText( Parser::TOC_PLACEHOLDER );
201 $out->addParserOutput( $pout );
203 $out->addHTML( $html );
205 $helptitle = $options[
'helptitle'] ??
null;
206 $html = self::fixHelpLinks( $out->getHTML(), $helptitle, $haveModules );
208 $out->addHTML( $html );
210 if ( $cacheKey !==
null ) {
212 $cache->set( $cacheKey, $out->getHTML(), $cacheHelpTimeout );
224 public static function fixHelpLinks( $html, $helptitle =
null, $localModules = [] ) {
225 return HtmlHelper::modifyElements(
227 static function ( SerializerNode $node ):
bool {
228 return $node->name ===
'a'
229 && isset( $node->attrs[
'href'] )
230 && !str_contains( $node->attrs[
'class'] ??
'',
'apihelp-linktrail' );
232 static function ( SerializerNode $node ) use ( $helptitle, $localModules ): SerializerNode {
233 $href = $node->attrs[
'href'];
237 $href = rawurldecode( $href );
238 }
while ( $old !== $href );
239 if ( preg_match(
'!Special:ApiHelp/([^&/|#]+)((?:#.*)?)!', $href, $m ) ) {
240 if ( isset( $localModules[$m[1]] ) ) {
241 $href = $m[2] ===
'' ?
'#' . $m[1] : $m[2];
242 } elseif ( $helptitle !==
null ) {
243 $href = Title::newFromText( str_replace(
'$1', $m[1], $helptitle ) . $m[2] )
251 $node->attrs[
'href'] = $href;
252 unset( $node->attrs[
'title'] );
268 private static function wrap(
Message $msg, $class, $tag =
'span' ) {
269 return Html::rawElement( $tag, [
'class' => $class ],
283 private static function getHelpInternal(
IContextSource $context, array $modules,
284 array $options, &$haveModules
288 $level = empty( $options[
'headerlevel'] ) ? 2 : $options[
'headerlevel'];
289 if ( empty( $options[
'tocnumber'] ) ) {
290 $tocnumber = [ 2 => 0 ];
292 $tocnumber = &$options[
'tocnumber'];
295 foreach ( $modules as $module ) {
296 $paramValidator = $module->getMain()->getParamValidator();
297 $tocnumber[$level]++;
298 $path = $module->getModulePath();
299 $module->setContext( $context );
310 if ( empty( $options[
'noheader'] ) || !empty( $options[
'toc'] ) ) {
313 while ( isset( $haveModules[$anchor] ) ) {
314 $anchor =
$path .
'|' . ++$i;
317 if ( $module->isMain() ) {
318 $headerContent = $context->
msg(
'api-help-main-header' )->parse();
320 'class' =>
'apihelp-header',
323 $name = $module->getModuleName();
324 $headerContent = htmlspecialchars(
325 $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
"=$name"
327 if ( $module->getModulePrefix() !==
'' ) {
328 $headerContent .=
' ' .
329 $context->
msg(
'parentheses', $module->getModulePrefix() )->parse();
335 'class' => [
'apihelp-header',
'apihelp-module-name' ],
341 $headerAttr[
'id'] = $anchor;
344 $haveModules[$anchor] = [
345 'toclevel' => count( $tocnumber ),
348 'line' => $headerContent,
349 'number' => implode(
'.', $tocnumber ),
352 if ( empty( $options[
'noheader'] ) ) {
353 $help[
'header'] .= Html::rawElement(
354 'h' . min( 6, $level ),
360 $haveModules[
$path] =
true;
365 for ( $m = $module; $m !==
null; $m = $m->getParent() ) {
366 $name = $m->getModuleName();
367 if ( $name ===
'main_int' ) {
371 if ( count( $modules ) === 1 && $m === $modules[0] &&
372 !( !empty( $options[
'submodules'] ) && $m->getModuleManager() )
374 $link = Html::element(
'b', [
'dir' =>
'ltr',
'lang' =>
'en' ], $name );
376 $link = SpecialPage::getTitleFor(
'ApiHelp', $m->getModulePath() )->getLocalURL();
377 $link = Html::element(
'a',
378 [
'href' => $link,
'class' =>
'apihelp-linktrail',
'dir' =>
'ltr',
'lang' =>
'en' ],
383 array_unshift( $links, $link );
386 $help[
'header'] .= self::wrap(
387 $context->
msg(
'parentheses' )
388 ->rawParams( $context->
getLanguage()->pipeList( $links ) ),
389 'apihelp-linktrail',
'div'
393 $flags = $module->getHelpFlags();
394 $help[
'flags'] .= Html::openElement(
'div',
395 [
'class' => [
'apihelp-block',
'apihelp-flags' ] ] );
396 $msg = $context->
msg(
'api-help-flags' );
397 if ( !$msg->isDisabled() ) {
398 $help[
'flags'] .= self::wrap(
399 $msg->numParams( count( $flags ) ),
'apihelp-block-head',
'div'
402 $help[
'flags'] .= Html::openElement(
'ul' );
403 foreach ( $flags as $flag ) {
404 $help[
'flags'] .= Html::rawElement(
'li', [],
411 self::wrap( $context->
msg(
"api-help-flag-$flag" ),
"apihelp-flag-$flag" )
414 $sourceInfo = $module->getModuleSourceInfo();
416 if ( isset( $sourceInfo[
'namemsg'] ) ) {
417 $extname = $context->
msg( $sourceInfo[
'namemsg'] )->text();
420 $extname = Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'name'] );
422 $help[
'flags'] .= Html::rawElement(
'li', [],
424 $context->
msg(
'api-help-source', $extname, $sourceInfo[
'name'] ),
429 $link = SpecialPage::getTitleFor(
'Version',
'License/' . $sourceInfo[
'name'] );
430 if ( isset( $sourceInfo[
'license-name'] ) ) {
431 $msg = $context->
msg(
'api-help-license', $link,
432 Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'license-name'] )
434 } elseif ( ExtensionInfo::getLicenseFileNames( dirname( $sourceInfo[
'path'] ) ) ) {
435 $msg = $context->
msg(
'api-help-license-noname', $link );
437 $msg = $context->
msg(
'api-help-license-unknown' );
439 $help[
'flags'] .= Html::rawElement(
'li', [],
440 self::wrap( $msg,
'apihelp-license' )
443 $help[
'flags'] .= Html::rawElement(
'li', [],
444 self::wrap( $context->
msg(
'api-help-source-unknown' ),
'apihelp-source' )
446 $help[
'flags'] .= Html::rawElement(
'li', [],
447 self::wrap( $context->
msg(
'api-help-license-unknown' ),
'apihelp-license' )
450 $help[
'flags'] .= Html::closeElement(
'ul' );
451 $help[
'flags'] .= Html::closeElement(
'div' );
453 foreach ( $module->getFinalDescription() as $msg ) {
454 $msg->setContext( $context );
455 $help[
'description'] .= $msg->parseAsBlock();
458 $urls = $module->getHelpUrls();
460 if ( !is_array( $urls ) ) {
463 $help[
'help-urls'] .= Html::openElement(
'div',
464 [
'class' => [
'apihelp-block',
'apihelp-help-urls' ] ]
466 $msg = $context->
msg(
'api-help-help-urls' );
467 if ( !$msg->isDisabled() ) {
468 $help[
'help-urls'] .= self::wrap(
469 $msg->numParams( count( $urls ) ),
'apihelp-block-head',
'div'
472 $help[
'help-urls'] .= Html::openElement(
'ul' );
473 foreach ( $urls as
$url ) {
474 $help[
'help-urls'] .= Html::rawElement(
'li', [],
475 Html::element(
'a', [
'href' =>
$url,
'dir' =>
'ltr' ],
$url )
478 $help[
'help-urls'] .= Html::closeElement(
'ul' );
479 $help[
'help-urls'] .= Html::closeElement(
'div' );
482 $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
483 $dynamicParams = $module->dynamicParameterDocumentation();
485 if (
$params || $dynamicParams !==
null ) {
486 $help[
'parameters'] .= Html::openElement(
'div',
487 [
'class' => [
'apihelp-block',
'apihelp-parameters' ] ]
489 $msg = $context->
msg(
'api-help-parameters' );
490 if ( !$msg->isDisabled() ) {
491 $help[
'parameters'] .= self::wrap(
492 $msg->numParams( count(
$params ) ),
'apihelp-block-head',
'div'
494 if ( !$module->isMain() ) {
496 $help[
'parameters'] .= self::wrap(
497 $context->
msg(
'api-help-parameters-note' ),
'apihelp-block-header',
'div'
501 $help[
'parameters'] .= Html::openElement(
'dl' );
503 $descriptions = $module->getFinalParamDescription();
505 foreach (
$params as $name => $settings ) {
506 $settings = $paramValidator->normalizeSettings( $settings );
508 if ( $settings[ParamValidator::PARAM_TYPE] ===
'submodule' ) {
512 $encodedParamName = $module->encodeParamName( $name );
513 $paramNameAttribs = [
'dir' =>
'ltr',
'lang' =>
'en' ];
514 if ( isset( $anchor ) ) {
515 $paramNameAttribs[
'id'] =
"$anchor:$encodedParamName";
517 $help[
'parameters'] .= Html::rawElement(
'dt', [],
518 Html::element(
'span', $paramNameAttribs, $encodedParamName )
523 if ( isset( $descriptions[$name] ) ) {
524 foreach ( $descriptions[$name] as $msg ) {
525 $msg->setContext( $context );
526 $description[] = $msg->parseAsBlock();
529 if ( !array_filter( $description ) ) {
530 $description = [ self::wrap(
531 $context->
msg(
'api-help-param-no-description' ),
537 if ( !empty( $settings[ParamValidator::PARAM_DEPRECATED] ) ) {
538 $help[
'parameters'] .= Html::openElement(
'dd',
539 [
'class' =>
'info' ] );
540 $help[
'parameters'] .= self::wrap(
541 $context->
msg(
'api-help-param-deprecated' ),
542 'apihelp-deprecated',
'strong'
544 $help[
'parameters'] .= Html::closeElement(
'dd' );
547 if ( $description ) {
548 $description = implode(
'', $description );
549 $description = preg_replace(
'!\s*</([oud]l)>\s*<\1>\s*!',
"\n", $description );
550 $help[
'parameters'] .= Html::rawElement(
'dd',
551 [
'class' =>
'description' ], $description );
556 $paramHelp = $paramValidator->getHelpInfo( $module, $name, $settings, [] );
558 unset( $paramHelp[ParamValidator::PARAM_DEPRECATED] );
560 if ( isset( $paramHelp[ParamValidator::PARAM_REQUIRED] ) ) {
561 $paramHelp[ParamValidator::PARAM_REQUIRED]->setContext( $context );
562 $info[] = $paramHelp[ParamValidator::PARAM_REQUIRED];
563 unset( $paramHelp[ParamValidator::PARAM_REQUIRED] );
567 if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
568 foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
569 $tag = array_shift( $i );
570 $info[] = $context->
msg(
"apihelp-{$path}-paraminfo-{$tag}" )
571 ->numParams( count( $i ) )
572 ->params( $context->
getLanguage()->commaList( $i ) )
573 ->params( $module->getModulePrefix() )
579 if ( !empty( $settings[ApiBase::PARAM_TEMPLATE_VARS] ) ) {
581 $msg =
'api-help-param-templated-var-first';
582 foreach ( $settings[ApiBase::PARAM_TEMPLATE_VARS] as $k => $v ) {
583 $vars[] = $context->
msg( $msg, $k, $module->encodeParamName( $v ) );
584 $msg =
'api-help-param-templated-var';
586 $info[] = $context->
msg(
'api-help-param-templated' )
587 ->numParams( count( $vars ) )
588 ->params( Message::listParam( $vars ) )
593 foreach ( $paramHelp as $m ) {
594 $m->setContext( $context );
595 $info[] = $m->parse();
598 foreach ( $info as $i ) {
599 $help[
'parameters'] .= Html::rawElement(
'dd', [
'class' =>
'info' ], $i );
603 if ( $dynamicParams !==
null ) {
604 $dynamicParams = $context->
msg(
605 Message::newFromSpecifier( $dynamicParams ),
606 $module->getModulePrefix(),
607 $module->getModuleName(),
608 $module->getModulePath()
610 $help[
'parameters'] .= Html::element(
'dt', [],
'*' );
611 $help[
'parameters'] .= Html::rawElement(
'dd',
612 [
'class' =>
'description' ], $dynamicParams->parse() );
615 $help[
'parameters'] .= Html::closeElement(
'dl' );
616 $help[
'parameters'] .= Html::closeElement(
'div' );
619 $examples = $module->getExamplesMessages();
621 $help[
'examples'] .= Html::openElement(
'div',
622 [
'class' => [
'apihelp-block',
'apihelp-examples' ] ] );
623 $msg = $context->
msg(
'api-help-examples' );
624 if ( !$msg->isDisabled() ) {
625 $help[
'examples'] .= self::wrap(
626 $msg->numParams( count( $examples ) ),
'apihelp-block-head',
'div'
630 $help[
'examples'] .= Html::openElement(
'dl' );
631 foreach ( $examples as $qs => $msg ) {
632 $msg = $context->
msg(
633 Message::newFromSpecifier( $msg ),
634 $module->getModulePrefix(),
635 $module->getModuleName(),
636 $module->getModulePath()
640 $sandbox = SpecialPage::getTitleFor(
'ApiSandbox' )->getLocalURL() .
'#' . $qs;
641 $help[
'examples'] .= Html::rawElement(
'dt', [], $msg->parse() );
642 $help[
'examples'] .= Html::rawElement(
'dd', [],
643 Html::element(
'a', [
647 ],
"api.php?$qs" ) .
' ' .
648 Html::rawElement(
'a', [
'href' => $sandbox ],
649 $context->
msg(
'api-help-open-in-apisandbox' )->parse() )
652 $help[
'examples'] .= Html::closeElement(
'dl' );
653 $help[
'examples'] .= Html::closeElement(
'div' );
656 $subtocnumber = $tocnumber;
657 $subtocnumber[$level + 1] = 0;
659 'submodules' => $options[
'recursivesubmodules'],
660 'headerlevel' => $level + 1,
661 'tocnumber' => &$subtocnumber,
666 if ( $options[
'submodules'] && $module->getModuleManager() ) {
667 $manager = $module->getModuleManager();
669 foreach ( $groups as $group ) {
670 $names = $manager->getNames( $group );
672 foreach ( $names as $name ) {
673 $submodules[] = $manager->getModule( $name );
676 $help[
'submodules'] .= self::getHelpInternal(
684 $module->modifyHelp( $help, $suboptions, $haveModules );
686 $module->getHookRunner()->onAPIHelpModifyOutput( $module, $help,
687 $suboptions, $haveModules );
689 $out .= implode(
"\n", $help );
710 $errorPrinter = $main->createPrinterByName( $main->getParameter(
'format' ) );
717 ParamValidator::PARAM_DEFAULT =>
'main',
718 ParamValidator::PARAM_ISMULTI =>
true,
720 'submodules' =>
false,
721 'recursivesubmodules' =>
false,
730 =>
'apihelp-help-example-main',
731 'action=help&modules=query&submodules=1'
732 =>
'apihelp-help-example-submodules',
733 'action=help&recursivesubmodules=1'
734 =>
'apihelp-help-example-recursive',
735 'action=help&modules=help'
736 =>
'apihelp-help-example-help',
737 'action=help&modules=query+info|query+categorymembers'
738 =>
'apihelp-help-example-query',
744 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Main_page',
745 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:FAQ',
746 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Quick_start_guide',
This is the main API class, used for both external and internal processing.