+ /**
+ * Recursively-called function to actually construct the help
+ *
+ * @param IContextSource $context
+ * @param ApiBase[] $modules
+ * @param array $options
+ * @param array &$haveModules
+ * @return string
+ */
+ private static function getHelpInternal( IContextSource $context, array $modules,
+ array $options, &$haveModules
+ ) {
+ $out = '';
+
+ $level = empty( $options['headerlevel'] ) ? 2 : $options['headerlevel'];
+ if ( empty( $options['tocnumber'] ) ) {
+ $tocnumber = [ 2 => 0 ];
+ } else {
+ $tocnumber = &$options['tocnumber'];
+ }
+
+ foreach ( $modules as $module ) {
+ $tocnumber[$level]++;
+ $path = $module->getModulePath();
+ $module->setContext( $context );
+ $help = [
+ 'header' => '',
+ 'flags' => '',
+ 'description' => '',
+ 'help-urls' => '',
+ 'parameters' => '',
+ 'examples' => '',
+ 'submodules' => '',
+ ];
+
+ if ( empty( $options['noheader'] ) || !empty( $options['toc'] ) ) {
+ $anchor = $path;
+ $i = 1;
+ while ( isset( $haveModules[$anchor] ) ) {
+ $anchor = $path . '|' . ++$i;
+ }
+
+ if ( $module->isMain() ) {
+ $headerContent = $context->msg( 'api-help-main-header' )->parse();
+ $headerAttr = [
+ 'class' => 'apihelp-header',
+ ];
+ } else {
+ $name = $module->getModuleName();
+ $headerContent = $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
+ "=$name";
+ if ( $module->getModulePrefix() !== '' ) {
+ $headerContent .= ' ' .
+ $context->msg( 'parentheses', $module->getModulePrefix() )->parse();
+ }
+ // Module names are always in English and not localized,
+ // so English language and direction must be set explicitly,
+ // otherwise parentheses will get broken in RTL wikis
+ $headerAttr = [
+ 'class' => 'apihelp-header apihelp-module-name',
+ 'dir' => 'ltr',
+ 'lang' => 'en',
+ ];
+ }
+
+ $headerAttr['id'] = $anchor;
+
+ $haveModules[$anchor] = [
+ 'toclevel' => count( $tocnumber ),
+ 'level' => $level,
+ 'anchor' => $anchor,
+ 'line' => $headerContent,
+ 'number' => implode( '.', $tocnumber ),
+ 'index' => false,
+ ];
+ if ( empty( $options['noheader'] ) ) {
+ $help['header'] .= Html::element(
+ 'h' . min( 6, $level ),
+ $headerAttr,
+ $headerContent
+ );
+ }
+ } else {
+ $haveModules[$path] = true;
+ }
+
+ $links = [];
+ $any = false;
+ for ( $m = $module; $m !== null; $m = $m->getParent() ) {
+ $name = $m->getModuleName();
+ if ( $name === 'main_int' ) {
+ $name = 'main';
+ }
+
+ if ( count( $modules ) === 1 && $m === $modules[0] &&
+ !( !empty( $options['submodules'] ) && $m->getModuleManager() )
+ ) {
+ $link = Html::element( 'b', [ 'dir' => 'ltr', 'lang' => 'en' ], $name );
+ } else {
+ $link = SpecialPage::getTitleFor( 'ApiHelp', $m->getModulePath() )->getLocalURL();
+ $link = Html::element( 'a',
+ [ 'href' => $link, 'class' => 'apihelp-linktrail', 'dir' => 'ltr', 'lang' => 'en' ],
+ $name
+ );
+ $any = true;
+ }
+ array_unshift( $links, $link );
+ }
+ if ( $any ) {
+ $help['header'] .= self::wrap(
+ $context->msg( 'parentheses' )
+ ->rawParams( $context->getLanguage()->pipeList( $links ) ),
+ 'apihelp-linktrail', 'div'
+ );
+ }
+
+ $flags = $module->getHelpFlags();
+ $help['flags'] .= Html::openElement( 'div',
+ [ 'class' => 'apihelp-block apihelp-flags' ] );
+ $msg = $context->msg( 'api-help-flags' );
+ if ( !$msg->isDisabled() ) {
+ $help['flags'] .= self::wrap(
+ $msg->numParams( count( $flags ) ), 'apihelp-block-head', 'div'
+ );
+ }
+ $help['flags'] .= Html::openElement( 'ul' );
+ foreach ( $flags as $flag ) {
+ $help['flags'] .= Html::rawElement( 'li', null,
+ self::wrap( $context->msg( "api-help-flag-$flag" ), "apihelp-flag-$flag" )
+ );
+ }
+ $sourceInfo = $module->getModuleSourceInfo();
+ if ( $sourceInfo ) {
+ if ( isset( $sourceInfo['namemsg'] ) ) {
+ $extname = $context->msg( $sourceInfo['namemsg'] )->text();
+ } else {
+ // Probably English, so wrap it.
+ $extname = Html::element( 'span', [ 'dir' => 'ltr', 'lang' => 'en' ], $sourceInfo['name'] );
+ }
+ $help['flags'] .= Html::rawElement( 'li', null,
+ self::wrap(
+ $context->msg( 'api-help-source', $extname, $sourceInfo['name'] ),
+ 'apihelp-source'
+ )
+ );
+
+ $link = SpecialPage::getTitleFor( 'Version', 'License/' . $sourceInfo['name'] );
+ if ( isset( $sourceInfo['license-name'] ) ) {
+ $msg = $context->msg( 'api-help-license', $link,
+ Html::element( 'span', [ 'dir' => 'ltr', 'lang' => 'en' ], $sourceInfo['license-name'] )
+ );
+ } elseif ( SpecialVersion::getExtLicenseFileName( dirname( $sourceInfo['path'] ) ) ) {
+ $msg = $context->msg( 'api-help-license-noname', $link );
+ } else {
+ $msg = $context->msg( 'api-help-license-unknown' );
+ }
+ $help['flags'] .= Html::rawElement( 'li', null,
+ self::wrap( $msg, 'apihelp-license' )
+ );
+ } else {
+ $help['flags'] .= Html::rawElement( 'li', null,
+ self::wrap( $context->msg( 'api-help-source-unknown' ), 'apihelp-source' )
+ );
+ $help['flags'] .= Html::rawElement( 'li', null,
+ self::wrap( $context->msg( 'api-help-license-unknown' ), 'apihelp-license' )
+ );
+ }
+ $help['flags'] .= Html::closeElement( 'ul' );
+ $help['flags'] .= Html::closeElement( 'div' );
+
+ foreach ( $module->getFinalDescription() as $msg ) {
+ $msg->setContext( $context );
+ $help['description'] .= $msg->parseAsBlock();
+ }
+
+ $urls = $module->getHelpUrls();
+ if ( $urls ) {
+ $help['help-urls'] .= Html::openElement( 'div',
+ [ 'class' => 'apihelp-block apihelp-help-urls' ]
+ );
+ $msg = $context->msg( 'api-help-help-urls' );
+ if ( !$msg->isDisabled() ) {
+ $help['help-urls'] .= self::wrap(
+ $msg->numParams( count( $urls ) ), 'apihelp-block-head', 'div'
+ );
+ }
+ if ( !is_array( $urls ) ) {
+ $urls = [ $urls ];
+ }
+ $help['help-urls'] .= Html::openElement( 'ul' );
+ foreach ( $urls as $url ) {
+ $help['help-urls'] .= Html::rawElement( 'li', null,
+ Html::element( 'a', [ 'href' => $url, 'dir' => 'ltr' ], $url )
+ );
+ }
+ $help['help-urls'] .= Html::closeElement( 'ul' );
+ $help['help-urls'] .= Html::closeElement( 'div' );
+ }
+
+ $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+ $dynamicParams = $module->dynamicParameterDocumentation();
+ $groups = [];
+ if ( $params || $dynamicParams !== null ) {
+ $help['parameters'] .= Html::openElement( 'div',
+ [ 'class' => 'apihelp-block apihelp-parameters' ]
+ );
+ $msg = $context->msg( 'api-help-parameters' );
+ if ( !$msg->isDisabled() ) {
+ $help['parameters'] .= self::wrap(
+ $msg->numParams( count( $params ) ), 'apihelp-block-head', 'div'
+ );
+ }
+ $help['parameters'] .= Html::openElement( 'dl' );
+
+ $descriptions = $module->getFinalParamDescription();
+
+ foreach ( $params as $name => $settings ) {
+ if ( !is_array( $settings ) ) {
+ $settings = [ ApiBase::PARAM_DFLT => $settings ];
+ }
+
+ $help['parameters'] .= Html::rawElement( 'dt', null,
+ Html::element( 'span', [ 'dir' => 'ltr', 'lang' => 'en' ], $module->encodeParamName( $name ) )
+ );
+
+ // Add description
+ $description = [];
+ if ( isset( $descriptions[$name] ) ) {
+ foreach ( $descriptions[$name] as $msg ) {
+ $msg->setContext( $context );
+ $description[] = $msg->parseAsBlock();
+ }
+ }
+
+ // Add usage info
+ $info = [];
+
+ // Required?
+ if ( !empty( $settings[ApiBase::PARAM_REQUIRED] ) ) {
+ $info[] = $context->msg( 'api-help-param-required' )->parse();
+ }
+
+ // Custom info?
+ if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
+ foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
+ $tag = array_shift( $i );
+ $info[] = $context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
+ ->numParams( count( $i ) )
+ ->params( $context->getLanguage()->commaList( $i ) )
+ ->params( $module->getModulePrefix() )
+ ->parse();
+ }
+ }
+
+ // Type documentation
+ if ( !isset( $settings[ApiBase::PARAM_TYPE] ) ) {
+ $dflt = isset( $settings[ApiBase::PARAM_DFLT] )
+ ? $settings[ApiBase::PARAM_DFLT]
+ : null;
+ if ( is_bool( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'boolean';
+ } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'string';
+ } elseif ( is_int( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'integer';
+ }
+ }
+ if ( isset( $settings[ApiBase::PARAM_TYPE] ) ) {
+ $type = $settings[ApiBase::PARAM_TYPE];
+ $multi = !empty( $settings[ApiBase::PARAM_ISMULTI] );
+ $hintPipeSeparated = true;
+ $count = !empty( $settings[ApiBase::PARAM_ISMULTI_LIMIT2] )
+ ? $settings[ApiBase::PARAM_ISMULTI_LIMIT2] + 1
+ : ApiBase::LIMIT_SML2 + 1;
+
+ if ( is_array( $type ) ) {
+ $count = count( $type );
+ $deprecatedValues = isset( $settings[ApiBase::PARAM_DEPRECATED_VALUES] )
+ ? $settings[ApiBase::PARAM_DEPRECATED_VALUES]
+ : [];
+ $links = isset( $settings[ApiBase::PARAM_VALUE_LINKS] )
+ ? $settings[ApiBase::PARAM_VALUE_LINKS]
+ : [];
+ $values = array_map( function ( $v ) use ( $links, $deprecatedValues ) {
+ $attr = [];
+ if ( $v !== '' ) {
+ // We can't know whether this contains LTR or RTL text.
+ $attr['dir'] = 'auto';
+ }
+ if ( isset( $deprecatedValues[$v] ) ) {
+ $attr['class'] = 'apihelp-deprecated-value';
+ }
+ $ret = $attr ? Html::element( 'span', $attr, $v ) : $v;
+ if ( isset( $links[$v] ) ) {
+ $ret = "[[{$links[$v]}|$ret]]";
+ }
+ return $ret;
+ }, $type );
+ $i = array_search( '', $type, true );
+ if ( $i === false ) {
+ $values = $context->getLanguage()->commaList( $values );
+ } else {
+ unset( $values[$i] );
+ $values = $context->msg( 'api-help-param-list-can-be-empty' )
+ ->numParams( count( $values ) )
+ ->params( $context->getLanguage()->commaList( $values ) )
+ ->parse();
+ }
+ $info[] = $context->msg( 'api-help-param-list' )
+ ->params( $multi ? 2 : 1 )
+ ->params( $values )
+ ->parse();
+ $hintPipeSeparated = false;
+ } else {
+ switch ( $type ) {
+ case 'submodule':
+ $groups[] = $name;
+
+ if ( isset( $settings[ApiBase::PARAM_SUBMODULE_MAP] ) ) {
+ $map = $settings[ApiBase::PARAM_SUBMODULE_MAP];
+ $defaultAttrs = [];
+ } else {
+ $prefix = $module->isMain() ? '' : ( $module->getModulePath() . '+' );
+ $map = [];
+ foreach ( $module->getModuleManager()->getNames( $name ) as $submoduleName ) {
+ $map[$submoduleName] = $prefix . $submoduleName;
+ }
+ $defaultAttrs = [ 'dir' => 'ltr', 'lang' => 'en' ];
+ }
+ ksort( $map );
+
+ $submodules = [];
+ $deprecatedSubmodules = [];
+ foreach ( $map as $v => $m ) {
+ $attrs = $defaultAttrs;
+ $arr = &$submodules;
+ try {
+ $submod = $module->getModuleFromPath( $m );
+ if ( $submod ) {
+ if ( $submod->isDeprecated() ) {
+ $arr = &$deprecatedSubmodules;
+ $attrs['class'] = 'apihelp-deprecated-value';
+ }
+ }
+ } catch ( ApiUsageException $ex ) {
+ // Ignore
+ }
+ if ( $attrs ) {
+ $v = Html::element( 'span', $attrs, $v );
+ }
+ $arr[] = "[[Special:ApiHelp/{$m}|{$v}]]";
+ }
+ $submodules = array_merge( $submodules, $deprecatedSubmodules );
+ $count = count( $submodules );
+ $info[] = $context->msg( 'api-help-param-list' )
+ ->params( $multi ? 2 : 1 )
+ ->params( $context->getLanguage()->commaList( $submodules ) )
+ ->parse();
+ $hintPipeSeparated = false;
+ // No type message necessary, we have a list of values.
+ $type = null;
+ break;
+
+ case 'namespace':
+ $namespaces = MWNamespace::getValidNamespaces();
+ if ( isset( $settings[ApiBase::PARAM_EXTRA_NAMESPACES] ) &&
+ is_array( $settings[ApiBase::PARAM_EXTRA_NAMESPACES] )
+ ) {
+ $namespaces = array_merge( $namespaces, $settings[ApiBase::PARAM_EXTRA_NAMESPACES] );
+ }
+ sort( $namespaces );
+ $count = count( $namespaces );
+ $info[] = $context->msg( 'api-help-param-list' )
+ ->params( $multi ? 2 : 1 )
+ ->params( $context->getLanguage()->commaList( $namespaces ) )
+ ->parse();
+ $hintPipeSeparated = false;
+ // No type message necessary, we have a list of values.
+ $type = null;
+ break;
+
+ case 'tags':
+ $tags = ChangeTags::listExplicitlyDefinedTags();
+ $count = count( $tags );
+ $info[] = $context->msg( 'api-help-param-list' )
+ ->params( $multi ? 2 : 1 )
+ ->params( $context->getLanguage()->commaList( $tags ) )
+ ->parse();
+ $hintPipeSeparated = false;
+ $type = null;
+ break;
+
+ case 'limit':
+ if ( isset( $settings[ApiBase::PARAM_MAX2] ) ) {
+ $info[] = $context->msg( 'api-help-param-limit2' )
+ ->numParams( $settings[ApiBase::PARAM_MAX] )
+ ->numParams( $settings[ApiBase::PARAM_MAX2] )
+ ->parse();
+ } else {
+ $info[] = $context->msg( 'api-help-param-limit' )
+ ->numParams( $settings[ApiBase::PARAM_MAX] )
+ ->parse();
+ }
+ break;
+
+ case 'integer':
+ // Possible messages:
+ // api-help-param-integer-min,
+ // api-help-param-integer-max,
+ // api-help-param-integer-minmax
+ $suffix = '';
+ $min = $max = 0;
+ if ( isset( $settings[ApiBase::PARAM_MIN] ) ) {
+ $suffix .= 'min';
+ $min = $settings[ApiBase::PARAM_MIN];
+ }
+ if ( isset( $settings[ApiBase::PARAM_MAX] ) ) {
+ $suffix .= 'max';
+ $max = $settings[ApiBase::PARAM_MAX];
+ }
+ if ( $suffix !== '' ) {
+ $info[] =
+ $context->msg( "api-help-param-integer-$suffix" )
+ ->params( $multi ? 2 : 1 )
+ ->numParams( $min, $max )
+ ->parse();
+ }
+ break;
+
+ case 'upload':
+ $info[] = $context->msg( 'api-help-param-upload' )
+ ->parse();
+ // No type message necessary, api-help-param-upload should handle it.
+ $type = null;
+ break;
+
+ case 'string':
+ case 'text':
+ // Displaying a type message here would be useless.
+ $type = null;
+ break;
+ }
+ }
+
+ // Add type. Messages for grep: api-help-param-type-limit
+ // api-help-param-type-integer api-help-param-type-boolean
+ // api-help-param-type-timestamp api-help-param-type-user
+ // api-help-param-type-password
+ if ( is_string( $type ) ) {
+ $msg = $context->msg( "api-help-param-type-$type" );
+ if ( !$msg->isDisabled() ) {
+ $info[] = $msg->params( $multi ? 2 : 1 )->parse();
+ }
+ }
+
+ if ( $multi ) {
+ $extra = [];
+ $lowcount = !empty( $settings[ApiBase::PARAM_ISMULTI_LIMIT1] )
+ ? $settings[ApiBase::PARAM_ISMULTI_LIMIT1]
+ : ApiBase::LIMIT_SML1;
+ $highcount = !empty( $settings[ApiBase::PARAM_ISMULTI_LIMIT2] )
+ ? $settings[ApiBase::PARAM_ISMULTI_LIMIT2]
+ : ApiBase::LIMIT_SML2;
+
+ if ( $hintPipeSeparated ) {
+ $extra[] = $context->msg( 'api-help-param-multi-separate' )->parse();
+ }
+ if ( $count > $lowcount ) {
+ if ( $lowcount === $highcount ) {
+ $msg = $context->msg( 'api-help-param-multi-max-simple' )
+ ->numParams( $lowcount );
+ } else {
+ $msg = $context->msg( 'api-help-param-multi-max' )
+ ->numParams( $lowcount, $highcount );
+ }
+ $extra[] = $msg->parse();
+ }
+ if ( $extra ) {
+ $info[] = implode( ' ', $extra );
+ }
+
+ $allowAll = isset( $settings[ApiBase::PARAM_ALL] )
+ ? $settings[ApiBase::PARAM_ALL]
+ : false;
+ if ( $allowAll || $settings[ApiBase::PARAM_TYPE] === 'namespace' ) {
+ if ( $settings[ApiBase::PARAM_TYPE] === 'namespace' ) {
+ $allSpecifier = ApiBase::ALL_DEFAULT_STRING;
+ } else {
+ $allSpecifier = ( is_string( $allowAll ) ? $allowAll : ApiBase::ALL_DEFAULT_STRING );
+ }
+ $info[] = $context->msg( 'api-help-param-multi-all' )
+ ->params( $allSpecifier )
+ ->parse();
+ }
+ }
+ }