23 use HtmlFormatter\HtmlFormatter;
37 foreach ( $params[
'modules'] as
$path ) {
43 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
44 $context->setSkin( $skinFactory->makeSkin(
'apioutput' ) );
48 $out->setRobotPolicy(
'noindex,nofollow' );
49 $out->setCopyrightUrl(
'https://www.mediawiki.org/wiki/Special:MyLanguage/Copyright' );
57 $html = ob_get_clean();
60 if ( $params[
'wrap'] ) {
62 'mime' =>
'text/html',
63 'filename' =>
'api-help.html',
71 'Types' => [
'AssocAsObject' =>
true ],
74 $errors = array_filter( [
75 'errors' => $this->
getResult()->getResultData( [
'errors' ], $transform ),
76 'warnings' => $this->
getResult()->getResultData( [
'warnings' ], $transform ),
82 $json = str_replace(
'--',
'-\u002D', $json );
83 $html =
"<!-- API warnings and errors:\n$json\n-->\n$html";
118 $out->addModuleStyles( [
120 'mediawiki.apipretty',
122 if ( !empty( $options[
'toc'] ) ) {
123 $out->addModuleStyles(
'mediawiki.toc.styles' );
125 $out->setPageTitle(
$context->
msg(
'api-help-title' ) );
127 $services = MediaWikiServices::getInstance();
128 $cache = $services->getMainWANObjectCache();
131 $options[
'recursivesubmodules'] &&
135 if ( $cacheHelpTimeout > 0 ) {
138 (
int)!empty( $options[
'toc'] ),
140 $cached =
$cache->get( $cacheKey );
142 $out->addHTML( $cached );
147 if ( $out->getHTML() !==
'' ) {
153 $options[
'recursivesubmodules'] = !empty( $options[
'recursivesubmodules'] );
154 $options[
'submodules'] = $options[
'recursivesubmodules'] || !empty( $options[
'submodules'] );
157 if ( empty( $options[
'nolead'] ) ) {
159 if ( !$msg->isDisabled() ) {
160 $out->addHTML( $msg->parseAsBlock() );
166 if ( !empty( $options[
'toc'] ) && $haveModules ) {
169 $out->addHTML( $html );
171 $helptitle = $options[
'helptitle'] ??
null;
174 $out->addHTML( $html );
176 if ( $cacheKey !==
null ) {
177 $cache->set( $cacheKey, $out->getHTML(), $cacheHelpTimeout );
189 public static function fixHelpLinks( $html, $helptitle =
null, $localModules = [] ) {
190 $formatter =
new HtmlFormatter( $html );
191 $doc = $formatter->getDoc();
192 $xpath =
new DOMXPath( $doc );
193 $nodes = $xpath->query(
'//a[@href][not(contains(@class,\'apihelp-linktrail\'))]' );
194 foreach ( $nodes as $node ) {
195 $href = $node->getAttribute(
'href' );
198 $href = rawurldecode( $href );
199 }
while ( $old !== $href );
200 if ( preg_match(
'!Special:ApiHelp/([^&/|#]+)((?:#.*)?)!', $href, $m ) ) {
201 if ( isset( $localModules[$m[1]] ) ) {
202 $href = $m[2] ===
'' ?
'#' . $m[1] : $m[2];
203 } elseif ( $helptitle !==
null ) {
212 $node->setAttribute(
'href', $href );
213 $node->removeAttribute(
'title' );
217 return $formatter->getText();
228 private static function wrap(
Message $msg, $class, $tag =
'span' ) {
229 return Html::rawElement( $tag, [
'class' => $class ],
244 array $options, &$haveModules
248 $level = empty( $options[
'headerlevel'] ) ? 2 : $options[
'headerlevel'];
249 if ( empty( $options[
'tocnumber'] ) ) {
250 $tocnumber = [ 2 => 0 ];
252 $tocnumber = &$options[
'tocnumber'];
256 $tocnumber[$level]++;
257 $path = $module->getModulePath();
269 if ( empty( $options[
'noheader'] ) || !empty( $options[
'toc'] ) ) {
272 while ( isset( $haveModules[$anchor] ) ) {
273 $anchor =
$path .
'|' . ++$i;
276 if ( $module->isMain() ) {
277 $headerContent =
$context->
msg(
'api-help-main-header' )->parse();
279 'class' =>
'apihelp-header',
282 $name = $module->getModuleName();
283 $headerContent = $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
285 if ( $module->getModulePrefix() !==
'' ) {
286 $headerContent .=
' ' .
287 $context->
msg(
'parentheses', $module->getModulePrefix() )->parse();
293 'class' =>
'apihelp-header apihelp-module-name',
299 $headerAttr[
'id'] = $anchor;
301 $haveModules[$anchor] = [
302 'toclevel' => count( $tocnumber ),
305 'line' => $headerContent,
306 'number' => implode(
'.', $tocnumber ),
309 if ( empty( $options[
'noheader'] ) ) {
310 $help[
'header'] .= Html::element(
311 'h' . min( 6, $level ),
317 $haveModules[
$path] =
true;
322 for ( $m = $module; $m !==
null; $m = $m->getParent() ) {
323 $name = $m->getModuleName();
324 if ( $name ===
'main_int' ) {
329 !( !empty( $options[
'submodules'] ) && $m->getModuleManager() )
331 $link = Html::element(
'b', [
'dir' =>
'ltr',
'lang' =>
'en' ], $name );
334 $link = Html::element(
'a',
335 [
'href' => $link,
'class' =>
'apihelp-linktrail',
'dir' =>
'ltr',
'lang' =>
'en' ],
340 array_unshift( $links, $link );
346 'apihelp-linktrail',
'div'
350 $flags = $module->getHelpFlags();
351 $help[
'flags'] .= Html::openElement(
'div',
352 [
'class' =>
'apihelp-block apihelp-flags' ] );
354 if ( !$msg->isDisabled() ) {
356 $msg->numParams( count( $flags ) ),
'apihelp-block-head',
'div'
359 $help[
'flags'] .= Html::openElement(
'ul' );
360 foreach ( $flags as $flag ) {
361 $help[
'flags'] .= Html::rawElement(
'li',
null,
362 self::wrap(
$context->
msg(
"api-help-flag-$flag" ),
"apihelp-flag-$flag" )
365 $sourceInfo = $module->getModuleSourceInfo();
367 if ( isset( $sourceInfo[
'namemsg'] ) ) {
368 $extname =
$context->
msg( $sourceInfo[
'namemsg'] )->text();
371 $extname = Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'name'] );
373 $help[
'flags'] .= Html::rawElement(
'li',
null,
375 $context->
msg(
'api-help-source', $extname, $sourceInfo[
'name'] ),
381 if ( isset( $sourceInfo[
'license-name'] ) ) {
383 Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $sourceInfo[
'license-name'] )
386 $msg =
$context->
msg(
'api-help-license-noname', $link );
388 $msg =
$context->
msg(
'api-help-license-unknown' );
390 $help[
'flags'] .= Html::rawElement(
'li',
null,
391 self::wrap( $msg,
'apihelp-license' )
394 $help[
'flags'] .= Html::rawElement(
'li',
null,
395 self::wrap(
$context->
msg(
'api-help-source-unknown' ),
'apihelp-source' )
397 $help[
'flags'] .= Html::rawElement(
'li',
null,
398 self::wrap(
$context->
msg(
'api-help-license-unknown' ),
'apihelp-license' )
401 $help[
'flags'] .= Html::closeElement(
'ul' );
402 $help[
'flags'] .= Html::closeElement(
'div' );
404 foreach ( $module->getFinalDescription() as $msg ) {
406 $help[
'description'] .= $msg->parseAsBlock();
409 $urls = $module->getHelpUrls();
411 $help[
'help-urls'] .= Html::openElement(
'div',
412 [
'class' =>
'apihelp-block apihelp-help-urls' ]
415 if ( !$msg->isDisabled() ) {
417 $msg->numParams( count( $urls ) ),
'apihelp-block-head',
'div'
420 if ( !is_array( $urls ) ) {
423 $help[
'help-urls'] .= Html::openElement(
'ul' );
424 foreach ( $urls as $url ) {
425 $help[
'help-urls'] .= Html::rawElement(
'li',
null,
426 Html::element(
'a', [
'href' => $url,
'dir' =>
'ltr' ], $url )
429 $help[
'help-urls'] .= Html::closeElement(
'ul' );
430 $help[
'help-urls'] .= Html::closeElement(
'div' );
434 $dynamicParams = $module->dynamicParameterDocumentation();
436 if ( $params || $dynamicParams !==
null ) {
437 $help[
'parameters'] .= Html::openElement(
'div',
438 [
'class' =>
'apihelp-block apihelp-parameters' ]
441 if ( !$msg->isDisabled() ) {
443 $msg->numParams( count( $params ) ),
'apihelp-block-head',
'div'
446 $help[
'parameters'] .= Html::openElement(
'dl' );
448 $descriptions = $module->getFinalParamDescription();
450 foreach ( $params as $name => $settings ) {
451 if ( !is_array( $settings ) ) {
455 $help[
'parameters'] .= Html::rawElement(
'dt',
null,
456 Html::element(
'span', [
'dir' =>
'ltr',
'lang' =>
'en' ], $module->encodeParamName( $name ) )
461 if ( isset( $descriptions[$name] ) ) {
462 foreach ( $descriptions[$name] as $msg ) {
464 $description[] = $msg->parseAsBlock();
473 $info[] =
$context->
msg(
'api-help-param-required' )->parse();
479 $tag = array_shift( $i );
480 $info[] =
$context->
msg(
"apihelp-{$path}-paraminfo-{$tag}" )
481 ->numParams( count( $i ) )
483 ->params( $module->getModulePrefix() )
491 $msg =
'api-help-param-templated-var-first';
493 $vars[] =
$context->
msg( $msg, $k, $module->encodeParamName( $v ) );
494 $msg =
'api-help-param-templated-var';
496 $info[] =
$context->
msg(
'api-help-param-templated' )
497 ->numParams( count( $vars ) )
498 ->params( Message::listParam( $vars ) )
505 if ( is_bool( $dflt ) ) {
507 } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
509 } elseif ( is_int( $dflt ) ) {
516 $hintPipeSeparated =
true;
521 if ( is_array(
$type ) ) {
522 $count = count(
$type );
525 $values = array_map(
function ( $v ) use ( $links, $deprecatedValues ) {
529 $attr[
'dir'] =
'auto';
531 if ( isset( $deprecatedValues[$v] ) ) {
532 $attr[
'class'] =
'apihelp-deprecated-value';
534 $ret = $attr ? Html::element(
'span', $attr, $v ) : $v;
535 if ( isset( $links[$v] ) ) {
536 $ret =
"[[{$links[$v]}|$ret]]";
540 $i = array_search(
'',
$type,
true );
541 if ( $i ===
false ) {
544 unset( $values[$i] );
545 $values =
$context->
msg(
'api-help-param-list-can-be-empty' )
546 ->numParams( count( $values ) )
551 ->params( $multi ? 2 : 1 )
554 $hintPipeSeparated =
false;
564 $prefix = $module->isMain() ?
'' : ( $module->getModulePath() .
'+' );
566 foreach ( $module->getModuleManager()->getNames( $name ) as $submoduleName ) {
567 $map[$submoduleName] = $prefix . $submoduleName;
569 $defaultAttrs = [
'dir' =>
'ltr',
'lang' =>
'en' ];
574 $deprecatedSubmodules = [];
575 foreach ( $map as $v => $m ) {
576 $attrs = $defaultAttrs;
579 $submod = $module->getModuleFromPath( $m );
580 if ( $submod && $submod->isDeprecated() ) {
581 $arr = &$deprecatedSubmodules;
582 $attrs[
'class'] =
'apihelp-deprecated-value';
588 $v = Html::element(
'span', $attrs, $v );
590 $arr[] =
"[[Special:ApiHelp/{$m}|{$v}]]";
592 $submodules = array_merge( $submodules, $deprecatedSubmodules );
593 $count = count( $submodules );
595 ->params( $multi ? 2 : 1 )
598 $hintPipeSeparated =
false;
604 $namespaces = MediaWikiServices::getInstance()->
605 getNamespaceInfo()->getValidNamespaces();
612 $count = count( $namespaces );
614 ->params( $multi ? 2 : 1 )
617 $hintPipeSeparated =
false;
624 $count = count( $tags );
626 ->params( $multi ? 2 : 1 )
629 $hintPipeSeparated =
false;
661 if ( $suffix !==
'' ) {
664 ->params( $multi ? 2 : 1 )
665 ->numParams( $min, $max )
689 if ( is_string(
$type ) ) {
690 $msg =
$context->
msg(
"api-help-param-type-$type" );
691 if ( !$msg->isDisabled() ) {
692 $info[] = $msg->params( $multi ? 2 : 1 )->parse();
705 if ( $hintPipeSeparated ) {
706 $extra[] =
$context->
msg(
'api-help-param-multi-separate' )->parse();
708 if ( $count > $lowcount ) {
709 if ( $lowcount === $highcount ) {
710 $msg =
$context->
msg(
'api-help-param-multi-max-simple' )
711 ->numParams( $lowcount );
714 ->numParams( $lowcount, $highcount );
716 $extra[] = $msg->parse();
719 $info[] = implode(
' ', $extra );
729 $info[] =
$context->
msg(
'api-help-param-multi-all' )
730 ->params( $allSpecifier )
736 if ( isset( $settings[self::PARAM_MAX_BYTES] ) ) {
737 $info[] =
$context->
msg(
'api-help-param-maxbytes' )
738 ->numParams( $settings[self::PARAM_MAX_BYTES] );
740 if ( isset( $settings[self::PARAM_MAX_CHARS] ) ) {
741 $info[] =
$context->
msg(
'api-help-param-maxchars' )
742 ->numParams( $settings[self::PARAM_MAX_CHARS] );
747 if ( $default ===
'' ) {
748 $info[] =
$context->
msg(
'api-help-param-default-empty' )
750 } elseif ( $default !==
null && $default !==
false ) {
752 $info[] =
$context->
msg(
'api-help-param-default' )
753 ->params( Html::element(
'span', [
'dir' =>
'auto' ], $default ) )
757 if ( !array_filter( $description ) ) {
766 $help[
'parameters'] .= Html::openElement(
'dd',
767 [
'class' =>
'info' ] );
770 'apihelp-deprecated',
'strong'
772 $help[
'parameters'] .= Html::closeElement(
'dd' );
775 if ( $description ) {
776 $description = implode(
'', $description );
777 $description = preg_replace(
'!\s*</([oud]l)>\s*<\1>\s*!',
"\n", $description );
778 $help[
'parameters'] .= Html::rawElement(
'dd',
779 [
'class' =>
'description' ], $description );
782 foreach ( $info as $i ) {
783 $help[
'parameters'] .= Html::rawElement(
'dd', [
'class' =>
'info' ], $i );
787 if ( $dynamicParams !==
null ) {
789 $module->getModulePrefix(),
790 $module->getModuleName(),
791 $module->getModulePath()
793 $help[
'parameters'] .= Html::element(
'dt',
null,
'*' );
794 $help[
'parameters'] .= Html::rawElement(
'dd',
795 [
'class' =>
'description' ], $dynamicParams->parse() );
798 $help[
'parameters'] .= Html::closeElement(
'dl' );
799 $help[
'parameters'] .= Html::closeElement(
'div' );
802 $examples = $module->getExamplesMessages();
804 $help[
'examples'] .= Html::openElement(
'div',
805 [
'class' =>
'apihelp-block apihelp-examples' ] );
807 if ( !$msg->isDisabled() ) {
809 $msg->numParams( count( $examples ) ),
'apihelp-block-head',
'div'
813 $help[
'examples'] .= Html::openElement(
'dl' );
814 foreach ( $examples as $qs => $msg ) {
816 $module->getModulePrefix(),
817 $module->getModuleName(),
818 $module->getModulePath()
823 $help[
'examples'] .= Html::rawElement(
'dt',
null, $msg->parse() );
824 $help[
'examples'] .= Html::rawElement(
'dd',
null,
825 Html::element(
'a', [
'href' => $link,
'dir' =>
'ltr' ],
"api.php?$qs" ) .
' ' .
826 Html::rawElement(
'a', [
'href' => $sandbox ],
827 $context->
msg(
'api-help-open-in-apisandbox' )->parse() )
830 $help[
'examples'] .= Html::closeElement(
'dl' );
831 $help[
'examples'] .= Html::closeElement(
'div' );
834 $subtocnumber = $tocnumber;
835 $subtocnumber[$level + 1] = 0;
837 'submodules' => $options[
'recursivesubmodules'],
838 'headerlevel' => $level + 1,
839 'tocnumber' => &$subtocnumber,
843 if ( $options[
'submodules'] && $module->getModuleManager() ) {
844 $manager = $module->getModuleManager();
846 foreach ( $groups as $group ) {
847 $names = $manager->getNames( $group );
849 foreach ( $names as $name ) {
850 $submodules[] = $manager->getModule( $name );
861 $module->modifyHelp(
$help, $suboptions, $haveModules );
863 Hooks::run(
'APIHelpModifyOutput', [ $module, &
$help, $suboptions, &$haveModules ] );
865 $out .= implode(
"\n",
$help );
881 if ( $params[
'wrap'] ) {
886 $errorPrinter = $main->createPrinterByName( $main->getParameter(
'format' ) );
896 'submodules' =>
false,
897 'recursivesubmodules' =>
false,
906 =>
'apihelp-help-example-main',
907 'action=help&modules=query&submodules=1'
908 =>
'apihelp-help-example-submodules',
909 'action=help&recursivesubmodules=1'
910 =>
'apihelp-help-example-recursive',
911 'action=help&modules=help'
912 =>
'apihelp-help-example-help',
913 'action=help&modules=query+info|query+categorymembers'
914 =>
'apihelp-help-example-query',
920 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Main_page',
921 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:FAQ',
922 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Quick_start_guide',