Completed
Branch master (62f6c6)
by
unknown
21:31
created

ApiHelp   F

Complexity

Total Complexity 127

Size/Duplication

Total Lines 790
Duplicated Lines 4.18 %

Coupling/Cohesion

Components 1
Dependencies 23

Importance

Changes 0
Metric Value
dl 33
loc 790
rs 1.2394
c 0
b 0
f 0
wmc 127
lcom 1
cbo 23

11 Methods

Rating   Name   Duplication   Size   Complexity  
B execute() 0 38 3
F getHelp() 0 66 17
C fixHelpLinks() 0 30 7
A wrap() 0 5 1
F getHelpInternal() 33 545 92
A shouldCheckMaxlag() 0 3 1
A isReadMode() 0 3 1
A getCustomPrinter() 0 10 2
A getAllowedParams() 0 12 1
A getExamplesMessages() 0 14 1
A getHelpUrls() 0 7 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ApiHelp often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ApiHelp, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 *
4
 *
5
 * Created on Aug 29, 2014
6
 *
7
 * Copyright © 2014 Brad Jorsch <[email protected]>
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License along
20
 * with this program; if not, write to the Free Software Foundation, Inc.,
21
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
 * http://www.gnu.org/copyleft/gpl.html
23
 *
24
 * @file
25
 */
26
27
use HtmlFormatter\HtmlFormatter;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, HtmlFormatter.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
28
29
/**
30
 * Class to output help for an API module
31
 *
32
 * @since 1.25 completely rewritten
33
 * @ingroup API
34
 */
35
class ApiHelp extends ApiBase {
36
	public function execute() {
37
		$params = $this->extractRequestParams();
38
		$modules = [];
39
40
		foreach ( $params['modules'] as $path ) {
41
			$modules[] = $this->getModuleFromPath( $path );
42
		}
43
44
		// Get the help
45
		$context = new DerivativeContext( $this->getMain()->getContext() );
46
		$context->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'apioutput' ) );
0 ignored issues
show
Deprecated Code introduced by
The method SkinFactory::getDefaultInstance() has been deprecated with message: in 1.27

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
47
		$context->setLanguage( $this->getMain()->getLanguage() );
48
		$context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
49
		$out = new OutputPage( $context );
50
		$out->setCopyrightUrl( 'https://www.mediawiki.org/wiki/Special:MyLanguage/Copyright' );
51
		$context->setOutput( $out );
52
53
		self::getHelp( $context, $modules, $params );
54
55
		// Grab the output from the skin
56
		ob_start();
57
		$context->getOutput()->output();
58
		$html = ob_get_clean();
59
60
		$result = $this->getResult();
61
		if ( $params['wrap'] ) {
62
			$data = [
63
				'mime' => 'text/html',
64
				'help' => $html,
65
			];
66
			ApiResult::setSubelementsList( $data, 'help' );
67
			$result->addValue( null, $this->getModuleName(), $data );
68
		} else {
69
			$result->reset();
70
			$result->addValue( null, 'text', $html, ApiResult::NO_SIZE_CHECK );
71
			$result->addValue( null, 'mime', 'text/html', ApiResult::NO_SIZE_CHECK );
72
		}
73
	}
74
75
	/**
76
	 * Generate help for the specified modules
77
	 *
78
	 * Help is placed into the OutputPage object returned by
79
	 * $context->getOutput().
80
	 *
81
	 * Recognized options include:
82
	 *  - headerlevel: (int) Header tag level
83
	 *  - nolead: (bool) Skip the inclusion of api-help-lead
84
	 *  - noheader: (bool) Skip the inclusion of the top-level section headers
85
	 *  - submodules: (bool) Include help for submodules of the current module
86
	 *  - recursivesubmodules: (bool) Include help for submodules recursively
87
	 *  - helptitle: (string) Title to link for additional modules' help. Should contain $1.
88
	 *  - toc: (bool) Include a table of contents
89
	 *
90
	 * @param IContextSource $context
91
	 * @param ApiBase[]|ApiBase $modules
92
	 * @param array $options Formatting options (described above)
93
	 * @return string
94
	 */
95
	public static function getHelp( IContextSource $context, $modules, array $options ) {
96
		global $wgContLang;
97
98
		if ( !is_array( $modules ) ) {
99
			$modules = [ $modules ];
100
		}
101
102
		$out = $context->getOutput();
103
		$out->addModuleStyles( 'mediawiki.hlist' );
104
		$out->addModuleStyles( 'mediawiki.apihelp' );
105
		if ( !empty( $options['toc'] ) ) {
106
			$out->addModules( 'mediawiki.toc' );
107
		}
108
		$out->setPageTitle( $context->msg( 'api-help-title' ) );
109
110
		$cache = ObjectCache::getMainWANInstance();
111
		$cacheKey = null;
112
		if ( count( $modules ) == 1 && $modules[0] instanceof ApiMain &&
113
			$options['recursivesubmodules'] && $context->getLanguage() === $wgContLang
114
		) {
115
			$cacheHelpTimeout = $context->getConfig()->get( 'APICacheHelpTimeout' );
116
			if ( $cacheHelpTimeout > 0 ) {
117
				// Get help text from cache if present
118
				$cacheKey = wfMemcKey( 'apihelp', $modules[0]->getModulePath(),
119
					(int)!empty( $options['toc'] ),
120
					str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) );
121
				$cached = $cache->get( $cacheKey );
122
				if ( $cached ) {
123
					$out->addHTML( $cached );
124
					return;
125
				}
126
			}
127
		}
128
		if ( $out->getHTML() !== '' ) {
129
			// Don't save to cache, there's someone else's content in the page
130
			// already
131
			$cacheKey = null;
132
		}
133
134
		$options['recursivesubmodules'] = !empty( $options['recursivesubmodules'] );
135
		$options['submodules'] = $options['recursivesubmodules'] || !empty( $options['submodules'] );
136
137
		// Prepend lead
138
		if ( empty( $options['nolead'] ) ) {
139
			$msg = $context->msg( 'api-help-lead' );
140
			if ( !$msg->isDisabled() ) {
141
				$out->addHTML( $msg->parseAsBlock() );
142
			}
143
		}
144
145
		$haveModules = [];
146
		$html = self::getHelpInternal( $context, $modules, $options, $haveModules );
147
		if ( !empty( $options['toc'] ) && $haveModules ) {
148
			$out->addHTML( Linker::generateTOC( $haveModules, $context->getLanguage() ) );
149
		}
150
		$out->addHTML( $html );
151
152
		$helptitle = isset( $options['helptitle'] ) ? $options['helptitle'] : null;
153
		$html = self::fixHelpLinks( $out->getHTML(), $helptitle, $haveModules );
0 ignored issues
show
Bug introduced by
It seems like $helptitle defined by isset($options['helptitl...ons['helptitle'] : null on line 152 can also be of type boolean; however, ApiHelp::fixHelpLinks() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
154
		$out->clearHTML();
155
		$out->addHTML( $html );
156
157
		if ( $cacheKey !== null ) {
158
			$cache->set( $cacheKey, $out->getHTML(), $cacheHelpTimeout );
0 ignored issues
show
Bug introduced by
The variable $cacheHelpTimeout does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
159
		}
160
	}
161
162
	/**
163
	 * Replace Special:ApiHelp links with links to api.php
164
	 *
165
	 * @param string $html
166
	 * @param string|null $helptitle Title to link to rather than api.php, must contain '$1'
167
	 * @param array $localModules Keys are modules to link within the current page, values are ignored
168
	 * @return string
169
	 */
170
	public static function fixHelpLinks( $html, $helptitle = null, $localModules = [] ) {
171
		$formatter = new HtmlFormatter( $html );
172
		$doc = $formatter->getDoc();
173
		$xpath = new DOMXPath( $doc );
174
		$nodes = $xpath->query( '//a[@href][not(contains(@class,\'apihelp-linktrail\'))]' );
175
		foreach ( $nodes as $node ) {
176
			$href = $node->getAttribute( 'href' );
177
			do {
178
				$old = $href;
179
				$href = rawurldecode( $href );
180
			} while ( $old !== $href );
181
			if ( preg_match( '!Special:ApiHelp/([^&/|#]+)((?:#.*)?)!', $href, $m ) ) {
182
				if ( isset( $localModules[$m[1]] ) ) {
183
					$href = $m[2] === '' ? '#' . $m[1] : $m[2];
184
				} elseif ( $helptitle !== null ) {
185
					$href = Title::newFromText( str_replace( '$1', $m[1], $helptitle ) . $m[2] )
186
						->getFullURL();
187
				} else {
188
					$href = wfAppendQuery( wfScript( 'api' ), [
189
						'action' => 'help',
190
						'modules' => $m[1],
191
					] ) . $m[2];
192
				}
193
				$node->setAttribute( 'href', $href );
194
				$node->removeAttribute( 'title' );
195
			}
196
		}
197
198
		return $formatter->getText();
199
	}
200
201
	/**
202
	 * Wrap a message in HTML with a class.
203
	 *
204
	 * @param Message $msg
205
	 * @param string $class
206
	 * @param string $tag
207
	 * @return string
208
	 */
209
	private static function wrap( Message $msg, $class, $tag = 'span' ) {
210
		return Html::rawElement( $tag, [ 'class' => $class ],
211
			$msg->parse()
212
		);
213
	}
214
215
	/**
216
	 * Recursively-called function to actually construct the help
217
	 *
218
	 * @param IContextSource $context
219
	 * @param ApiBase[] $modules
220
	 * @param array $options
221
	 * @param array &$haveModules
222
	 * @return string
223
	 */
224
	private static function getHelpInternal( IContextSource $context, array $modules,
225
		array $options, &$haveModules
226
	) {
227
		$out = '';
228
229
		$level = empty( $options['headerlevel'] ) ? 2 : $options['headerlevel'];
230
		if ( empty( $options['tocnumber'] ) ) {
231
			$tocnumber = [ 2 => 0 ];
232
		} else {
233
			$tocnumber = &$options['tocnumber'];
234
		}
235
236
		foreach ( $modules as $module ) {
237
			$tocnumber[$level]++;
238
			$path = $module->getModulePath();
239
			$module->setContext( $context );
240
			$help = [
241
				'header' => '',
242
				'flags' => '',
243
				'description' => '',
244
				'help-urls' => '',
245
				'parameters' => '',
246
				'examples' => '',
247
				'submodules' => '',
248
			];
249
250
			if ( empty( $options['noheader'] ) || !empty( $options['toc'] ) ) {
251
				$anchor = $path;
252
				$i = 1;
253
				while ( isset( $haveModules[$anchor] ) ) {
254
					$anchor = $path . '|' . ++$i;
255
				}
256
257
				if ( $module->isMain() ) {
258
					$headerContent = $context->msg( 'api-help-main-header' )->parse();
259
					$headerAttr = [
260
						'class' => 'apihelp-header',
261
					];
262
				} else {
263
					$name = $module->getModuleName();
264
					$headerContent = $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
265
						"=$name";
266
					if ( $module->getModulePrefix() !== '' ) {
267
						$headerContent .= ' ' .
268
							$context->msg( 'parentheses', $module->getModulePrefix() )->parse();
269
					}
270
					// Module names are always in English and not localized,
271
					// so English language and direction must be set explicitly,
272
					// otherwise parentheses will get broken in RTL wikis
273
					$headerAttr = [
274
						'class' => 'apihelp-header apihelp-module-name',
275
						'dir' => 'ltr',
276
						'lang' => 'en',
277
					];
278
				}
279
280
				$headerAttr['id'] = $anchor;
281
282
				$haveModules[$anchor] = [
283
					'toclevel' => count( $tocnumber ),
284
					'level' => $level,
285
					'anchor' => $anchor,
286
					'line' => $headerContent,
287
					'number' => implode( '.', $tocnumber ),
288
					'index' => false,
289
				];
290
				if ( empty( $options['noheader'] ) ) {
291
					$help['header'] .= Html::element(
292
						'h' . min( 6, $level ),
293
						$headerAttr,
294
						$headerContent
295
					);
296
				}
297
			} else {
298
				$haveModules[$path] = true;
299
			}
300
301
			$links = [];
302
			$any = false;
303
			for ( $m = $module; $m !== null; $m = $m->getParent() ) {
304
				$name = $m->getModuleName();
305
				if ( $name === 'main_int' ) {
306
					$name = 'main';
307
				}
308
309
				if ( count( $modules ) === 1 && $m === $modules[0] &&
310
					!( !empty( $options['submodules'] ) && $m->getModuleManager() )
311
				) {
312
					$link = Html::element( 'b', null, $name );
313
				} else {
314
					$link = SpecialPage::getTitleFor( 'ApiHelp', $m->getModulePath() )->getLocalURL();
315
					$link = Html::element( 'a',
316
						[ 'href' => $link, 'class' => 'apihelp-linktrail' ],
317
						$name
318
					);
319
					$any = true;
320
				}
321
				array_unshift( $links, $link );
322
			}
323
			if ( $any ) {
324
				$help['header'] .= self::wrap(
325
					$context->msg( 'parentheses' )
326
						->rawParams( $context->getLanguage()->pipeList( $links ) ),
327
					'apihelp-linktrail', 'div'
328
				);
329
			}
330
331
			$flags = $module->getHelpFlags();
332
			$help['flags'] .= Html::openElement( 'div',
333
				[ 'class' => 'apihelp-block apihelp-flags' ] );
334
			$msg = $context->msg( 'api-help-flags' );
335
			if ( !$msg->isDisabled() ) {
336
				$help['flags'] .= self::wrap(
337
					$msg->numParams( count( $flags ) ), 'apihelp-block-head', 'div'
338
				);
339
			}
340
			$help['flags'] .= Html::openElement( 'ul' );
341
			foreach ( $flags as $flag ) {
342
				$help['flags'] .= Html::rawElement( 'li', null,
343
					self::wrap( $context->msg( "api-help-flag-$flag" ), "apihelp-flag-$flag" )
344
				);
345
			}
346
			$sourceInfo = $module->getModuleSourceInfo();
347
			if ( $sourceInfo ) {
348
				if ( isset( $sourceInfo['namemsg'] ) ) {
349
					$extname = $context->msg( $sourceInfo['namemsg'] )->text();
350
				} else {
351
					$extname = $sourceInfo['name'];
352
				}
353
				$help['flags'] .= Html::rawElement( 'li', null,
354
					self::wrap(
355
						$context->msg( 'api-help-source', $extname, $sourceInfo['name'] ),
356
						'apihelp-source'
357
					)
358
				);
359
360
				$link = SpecialPage::getTitleFor( 'Version', 'License/' . $sourceInfo['name'] );
361
				if ( isset( $sourceInfo['license-name'] ) ) {
362
					$msg = $context->msg( 'api-help-license', $link, $sourceInfo['license-name'] );
363
				} elseif ( SpecialVersion::getExtLicenseFileName( dirname( $sourceInfo['path'] ) ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \SpecialVersion::getExtL...e($sourceInfo['path'])) of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
364
					$msg = $context->msg( 'api-help-license-noname', $link );
365
				} else {
366
					$msg = $context->msg( 'api-help-license-unknown' );
367
				}
368
				$help['flags'] .= Html::rawElement( 'li', null,
369
					self::wrap( $msg, 'apihelp-license' )
370
				);
371
			} else {
372
				$help['flags'] .= Html::rawElement( 'li', null,
373
					self::wrap( $context->msg( 'api-help-source-unknown' ), 'apihelp-source' )
374
				);
375
				$help['flags'] .= Html::rawElement( 'li', null,
376
					self::wrap( $context->msg( 'api-help-license-unknown' ), 'apihelp-license' )
377
				);
378
			}
379
			$help['flags'] .= Html::closeElement( 'ul' );
380
			$help['flags'] .= Html::closeElement( 'div' );
381
382
			foreach ( $module->getFinalDescription() as $msg ) {
383
				$msg->setContext( $context );
384
				$help['description'] .= $msg->parseAsBlock();
385
			}
386
387
			$urls = $module->getHelpUrls();
388
			if ( $urls ) {
389
				$help['help-urls'] .= Html::openElement( 'div',
390
					[ 'class' => 'apihelp-block apihelp-help-urls' ]
391
				);
392
				$msg = $context->msg( 'api-help-help-urls' );
393
				if ( !$msg->isDisabled() ) {
394
					$help['help-urls'] .= self::wrap(
395
						$msg->numParams( count( $urls ) ), 'apihelp-block-head', 'div'
396
					);
397
				}
398
				if ( !is_array( $urls ) ) {
399
					$urls = [ $urls ];
400
				}
401
				$help['help-urls'] .= Html::openElement( 'ul' );
402
				foreach ( $urls as $url ) {
403
					$help['help-urls'] .= Html::rawElement( 'li', null,
404
						Html::element( 'a', [ 'href' => $url ], $url )
405
					);
406
				}
407
				$help['help-urls'] .= Html::closeElement( 'ul' );
408
				$help['help-urls'] .= Html::closeElement( 'div' );
409
			}
410
411
			$params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
412
			$dynamicParams = $module->dynamicParameterDocumentation();
413
			$groups = [];
414
			if ( $params || $dynamicParams !== null ) {
415
				$help['parameters'] .= Html::openElement( 'div',
416
					[ 'class' => 'apihelp-block apihelp-parameters' ]
417
				);
418
				$msg = $context->msg( 'api-help-parameters' );
419
				if ( !$msg->isDisabled() ) {
420
					$help['parameters'] .= self::wrap(
421
						$msg->numParams( count( $params ) ), 'apihelp-block-head', 'div'
422
					);
423
				}
424
				$help['parameters'] .= Html::openElement( 'dl' );
425
426
				$descriptions = $module->getFinalParamDescription();
427
428
				foreach ( $params as $name => $settings ) {
429
					if ( !is_array( $settings ) ) {
430
						$settings = [ ApiBase::PARAM_DFLT => $settings ];
431
					}
432
433
					$help['parameters'] .= Html::element( 'dt', null,
434
						$module->encodeParamName( $name ) );
435
436
					// Add description
437
					$description = [];
438
					if ( isset( $descriptions[$name] ) ) {
439
						foreach ( $descriptions[$name] as $msg ) {
440
							$msg->setContext( $context );
441
							$description[] = $msg->parseAsBlock();
442
						}
443
					}
444
445
					// Add usage info
446
					$info = [];
447
448
					// Required?
449
					if ( !empty( $settings[ApiBase::PARAM_REQUIRED] ) ) {
450
						$info[] = $context->msg( 'api-help-param-required' )->parse();
451
					}
452
453
					// Custom info?
454
					if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
455
						foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
456
							$tag = array_shift( $i );
457
							$info[] = $context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
458
								->numParams( count( $i ) )
459
								->params( $context->getLanguage()->commaList( $i ) )
460
								->params( $module->getModulePrefix() )
461
								->parse();
462
						}
463
					}
464
465
					// Type documentation
466 View Code Duplication
					if ( !isset( $settings[ApiBase::PARAM_TYPE] ) ) {
467
						$dflt = isset( $settings[ApiBase::PARAM_DFLT] )
468
							? $settings[ApiBase::PARAM_DFLT]
469
							: null;
470
						if ( is_bool( $dflt ) ) {
471
							$settings[ApiBase::PARAM_TYPE] = 'boolean';
472
						} elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
473
							$settings[ApiBase::PARAM_TYPE] = 'string';
474
						} elseif ( is_int( $dflt ) ) {
475
							$settings[ApiBase::PARAM_TYPE] = 'integer';
476
						}
477
					}
478
					if ( isset( $settings[ApiBase::PARAM_TYPE] ) ) {
479
						$type = $settings[ApiBase::PARAM_TYPE];
480
						$multi = !empty( $settings[ApiBase::PARAM_ISMULTI] );
481
						$hintPipeSeparated = true;
482
						$count = ApiBase::LIMIT_SML2 + 1;
483
484
						if ( is_array( $type ) ) {
485
							$count = count( $type );
486
							$links = isset( $settings[ApiBase::PARAM_VALUE_LINKS] )
487
								? $settings[ApiBase::PARAM_VALUE_LINKS]
488
								: [];
489
							$type = array_map( function ( $v ) use ( $links ) {
490
								$ret = wfEscapeWikiText( $v );
491
								if ( isset( $links[$v] ) ) {
492
									$ret = "[[{$links[$v]}|$ret]]";
493
								}
494
								return $ret;
495
							}, $type );
496
							$i = array_search( '', $type, true );
497
							if ( $i === false ) {
498
								$type = $context->getLanguage()->commaList( $type );
499
							} else {
500
								unset( $type[$i] );
501
								$type = $context->msg( 'api-help-param-list-can-be-empty' )
502
									->numParams( count( $type ) )
503
									->params( $context->getLanguage()->commaList( $type ) )
504
									->parse();
505
							}
506
							$info[] = $context->msg( 'api-help-param-list' )
507
								->params( $multi ? 2 : 1 )
508
								->params( $type )
509
								->parse();
510
							$hintPipeSeparated = false;
511
						} else {
512
							switch ( $type ) {
513
								case 'submodule':
514
									$groups[] = $name;
515
									if ( isset( $settings[ApiBase::PARAM_SUBMODULE_MAP] ) ) {
516
										$map = $settings[ApiBase::PARAM_SUBMODULE_MAP];
517
										ksort( $map );
518
										$submodules = [];
519
										foreach ( $map as $v => $m ) {
520
											$submodules[] = "[[Special:ApiHelp/{$m}|{$v}]]";
521
										}
522
									} else {
523
										$submodules = $module->getModuleManager()->getNames( $name );
524
										sort( $submodules );
525
										$prefix = $module->isMain()
526
											? '' : ( $module->getModulePath() . '+' );
527
										$submodules = array_map( function ( $name ) use ( $prefix ) {
528
											return "[[Special:ApiHelp/{$prefix}{$name}|{$name}]]";
529
										}, $submodules );
530
									}
531
									$count = count( $submodules );
532
									$info[] = $context->msg( 'api-help-param-list' )
533
										->params( $multi ? 2 : 1 )
534
										->params( $context->getLanguage()->commaList( $submodules ) )
535
										->parse();
536
									$hintPipeSeparated = false;
537
									// No type message necessary, we have a list of values.
538
									$type = null;
539
									break;
540
541 View Code Duplication
								case 'namespace':
542
									$namespaces = MWNamespace::getValidNamespaces();
543
									$count = count( $namespaces );
544
									$info[] = $context->msg( 'api-help-param-list' )
545
										->params( $multi ? 2 : 1 )
546
										->params( $context->getLanguage()->commaList( $namespaces ) )
547
										->parse();
548
									$hintPipeSeparated = false;
549
									// No type message necessary, we have a list of values.
550
									$type = null;
551
									break;
552
553 View Code Duplication
								case 'tags':
554
									$tags = ChangeTags::listExplicitlyDefinedTags();
555
									$count = count( $tags );
556
									$info[] = $context->msg( 'api-help-param-list' )
557
										->params( $multi ? 2 : 1 )
558
										->params( $context->getLanguage()->commaList( $tags ) )
559
										->parse();
560
									$hintPipeSeparated = false;
561
									$type = null;
562
									break;
563
564
								case 'limit':
565
									if ( isset( $settings[ApiBase::PARAM_MAX2] ) ) {
566
										$info[] = $context->msg( 'api-help-param-limit2' )
567
											->numParams( $settings[ApiBase::PARAM_MAX] )
568
											->numParams( $settings[ApiBase::PARAM_MAX2] )
569
											->parse();
570
									} else {
571
										$info[] = $context->msg( 'api-help-param-limit' )
572
											->numParams( $settings[ApiBase::PARAM_MAX] )
573
											->parse();
574
									}
575
									break;
576
577
								case 'integer':
578
									// Possible messages:
579
									// api-help-param-integer-min,
580
									// api-help-param-integer-max,
581
									// api-help-param-integer-minmax
582
									$suffix = '';
583
									$min = $max = 0;
584
									if ( isset( $settings[ApiBase::PARAM_MIN] ) ) {
585
										$suffix .= 'min';
586
										$min = $settings[ApiBase::PARAM_MIN];
587
									}
588
									if ( isset( $settings[ApiBase::PARAM_MAX] ) ) {
589
										$suffix .= 'max';
590
										$max = $settings[ApiBase::PARAM_MAX];
591
									}
592
									if ( $suffix !== '' ) {
593
										$info[] =
594
											$context->msg( "api-help-param-integer-$suffix" )
595
												->params( $multi ? 2 : 1 )
596
												->numParams( $min, $max )
597
												->parse();
598
									}
599
									break;
600
601
								case 'upload':
602
									$info[] = $context->msg( 'api-help-param-upload' )
603
										->parse();
604
									// No type message necessary, api-help-param-upload should handle it.
605
									$type = null;
606
									break;
607
608
								case 'string':
609
								case 'text':
610
									// Displaying a type message here would be useless.
611
									$type = null;
612
									break;
613
							}
614
						}
615
616
						// Add type. Messages for grep: api-help-param-type-limit
617
						// api-help-param-type-integer api-help-param-type-boolean
618
						// api-help-param-type-timestamp api-help-param-type-user
619
						// api-help-param-type-password
620
						if ( is_string( $type ) ) {
621
							$msg = $context->msg( "api-help-param-type-$type" );
622
							if ( !$msg->isDisabled() ) {
623
								$info[] = $msg->params( $multi ? 2 : 1 )->parse();
624
							}
625
						}
626
627
						if ( $multi ) {
628
							$extra = [];
629
							if ( $hintPipeSeparated ) {
630
								$extra[] = $context->msg( 'api-help-param-multi-separate' )->parse();
631
							}
632
							if ( $count > ApiBase::LIMIT_SML1 ) {
633
								$extra[] = $context->msg( 'api-help-param-multi-max' )
634
									->numParams( ApiBase::LIMIT_SML1, ApiBase::LIMIT_SML2 )
635
									->parse();
636
							}
637
							if ( $extra ) {
638
								$info[] = implode( ' ', $extra );
639
							}
640
						}
641
					}
642
643
					// Add default
644
					$default = isset( $settings[ApiBase::PARAM_DFLT] )
645
						? $settings[ApiBase::PARAM_DFLT]
646
						: null;
647
					if ( $default === '' ) {
648
						$info[] = $context->msg( 'api-help-param-default-empty' )
649
							->parse();
650
					} elseif ( $default !== null && $default !== false ) {
651
						$info[] = $context->msg( 'api-help-param-default' )
652
							->params( wfEscapeWikiText( $default ) )
653
							->parse();
654
					}
655
656
					if ( !array_filter( $description ) ) {
657
						$description = [ self::wrap(
658
							$context->msg( 'api-help-param-no-description' ),
659
							'apihelp-empty'
660
						) ];
661
					}
662
663
					// Add "deprecated" flag
664
					if ( !empty( $settings[ApiBase::PARAM_DEPRECATED] ) ) {
665
						$help['parameters'] .= Html::openElement( 'dd',
666
							[ 'class' => 'info' ] );
667
						$help['parameters'] .= self::wrap(
668
							$context->msg( 'api-help-param-deprecated' ),
669
							'apihelp-deprecated', 'strong'
670
						);
671
						$help['parameters'] .= Html::closeElement( 'dd' );
672
					}
673
674
					if ( $description ) {
675
						$description = implode( '', $description );
676
						$description = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $description );
677
						$help['parameters'] .= Html::rawElement( 'dd',
678
							[ 'class' => 'description' ], $description );
679
					}
680
681
					foreach ( $info as $i ) {
682
						$help['parameters'] .= Html::rawElement( 'dd', [ 'class' => 'info' ], $i );
683
					}
684
				}
685
686
				if ( $dynamicParams !== null ) {
687
					$dynamicParams = ApiBase::makeMessage( $dynamicParams, $context, [
688
						$module->getModulePrefix(),
689
						$module->getModuleName(),
690
						$module->getModulePath()
691
					] );
692
					$help['parameters'] .= Html::element( 'dt', null, '*' );
693
					$help['parameters'] .= Html::rawElement( 'dd',
694
						[ 'class' => 'description' ], $dynamicParams->parse() );
695
				}
696
697
				$help['parameters'] .= Html::closeElement( 'dl' );
698
				$help['parameters'] .= Html::closeElement( 'div' );
699
			}
700
701
			$examples = $module->getExamplesMessages();
702
			if ( $examples ) {
703
				$help['examples'] .= Html::openElement( 'div',
704
					[ 'class' => 'apihelp-block apihelp-examples' ] );
705
				$msg = $context->msg( 'api-help-examples' );
706
				if ( !$msg->isDisabled() ) {
707
					$help['examples'] .= self::wrap(
708
						$msg->numParams( count( $examples ) ), 'apihelp-block-head', 'div'
709
					);
710
				}
711
712
				$help['examples'] .= Html::openElement( 'dl' );
713
				foreach ( $examples as $qs => $msg ) {
714
					$msg = ApiBase::makeMessage( $msg, $context, [
715
						$module->getModulePrefix(),
716
						$module->getModuleName(),
717
						$module->getModulePath()
718
					] );
719
720
					$link = wfAppendQuery( wfScript( 'api' ), $qs );
721
					$sandbox = SpecialPage::getTitleFor( 'ApiSandbox' )->getLocalURL() . '#' . $qs;
722
					$help['examples'] .= Html::rawElement( 'dt', null, $msg->parse() );
723
					$help['examples'] .= Html::rawElement( 'dd', null,
724
						Html::element( 'a', [ 'href' => $link ], "api.php?$qs" ) . ' ' .
725
						Html::rawElement( 'a', [ 'href' => $sandbox ],
726
							$context->msg( 'api-help-open-in-apisandbox' )->parse() )
727
					);
728
				}
729
				$help['examples'] .= Html::closeElement( 'dl' );
730
				$help['examples'] .= Html::closeElement( 'div' );
731
			}
732
733
			$subtocnumber = $tocnumber;
734
			$subtocnumber[$level + 1] = 0;
735
			$suboptions = [
736
				'submodules' => $options['recursivesubmodules'],
737
				'headerlevel' => $level + 1,
738
				'tocnumber' => &$subtocnumber,
739
				'noheader' => false,
740
			] + $options;
741
742
			if ( $options['submodules'] && $module->getModuleManager() ) {
743
				$manager = $module->getModuleManager();
744
				$submodules = [];
745
				foreach ( $groups as $group ) {
746
					$names = $manager->getNames( $group );
747
					sort( $names );
748
					foreach ( $names as $name ) {
749
						$submodules[] = $manager->getModule( $name );
750
					}
751
				}
752
				$help['submodules'] .= self::getHelpInternal(
753
					$context,
754
					$submodules,
755
					$suboptions,
756
					$haveModules
757
				);
758
			}
759
760
			$module->modifyHelp( $help, $suboptions, $haveModules );
761
762
			Hooks::run( 'APIHelpModifyOutput', [ $module, &$help, $suboptions, &$haveModules ] );
763
764
			$out .= implode( "\n", $help );
765
		}
766
767
		return $out;
768
	}
769
770
	public function shouldCheckMaxlag() {
771
		return false;
772
	}
773
774
	public function isReadMode() {
775
		return false;
776
	}
777
778
	public function getCustomPrinter() {
779
		$params = $this->extractRequestParams();
780
		if ( $params['wrap'] ) {
781
			return null;
782
		}
783
784
		$main = $this->getMain();
785
		$errorPrinter = $main->createPrinterByName( $main->getParameter( 'format' ) );
0 ignored issues
show
Bug introduced by
The method getParameter() cannot be called from this context as it is declared protected in class ApiBase.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
786
		return new ApiFormatRaw( $main, $errorPrinter );
787
	}
788
789
	public function getAllowedParams() {
790
		return [
791
			'modules' => [
792
				ApiBase::PARAM_DFLT => 'main',
793
				ApiBase::PARAM_ISMULTI => true,
794
			],
795
			'submodules' => false,
796
			'recursivesubmodules' => false,
797
			'wrap' => false,
798
			'toc' => false,
799
		];
800
	}
801
802
	protected function getExamplesMessages() {
803
		return [
804
			'action=help'
805
				=> 'apihelp-help-example-main',
806
			'action=help&modules=query&submodules=1'
807
				=> 'apihelp-help-example-submodules',
808
			'action=help&recursivesubmodules=1'
809
				=> 'apihelp-help-example-recursive',
810
			'action=help&modules=help'
811
				=> 'apihelp-help-example-help',
812
			'action=help&modules=query+info|query+categorymembers'
813
				=> 'apihelp-help-example-query',
814
		];
815
	}
816
817
	public function getHelpUrls() {
818
		return [
819
			'https://www.mediawiki.org/wiki/API:Main_page',
820
			'https://www.mediawiki.org/wiki/API:FAQ',
821
			'https://www.mediawiki.org/wiki/API:Quick_start_guide',
822
		];
823
	}
824
}
825