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