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

SpecialContributions::getForm()   F

Complexity

Conditions 20
Paths > 20000

Size

Total Lines 257
Code Lines 173

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 173
nc 49152
nop 0
dl 0
loc 257
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Implements Special:Contributions
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
 * @ingroup SpecialPage
22
 */
23
24
/**
25
 * Special:Contributions, show user contributions in a paged list
26
 *
27
 * @ingroup SpecialPage
28
 */
29
class SpecialContributions extends IncludableSpecialPage {
30
	protected $opts;
31
32
	public function __construct() {
33
		parent::__construct( 'Contributions' );
34
	}
35
36
	public function execute( $par ) {
37
		$this->setHeaders();
38
		$this->outputHeader();
39
		$out = $this->getOutput();
40
		$out->addModuleStyles( 'mediawiki.special' );
41
		$this->addHelpLink( 'Help:User contributions' );
42
43
		$this->opts = [];
44
		$request = $this->getRequest();
45
46
		if ( $par !== null ) {
47
			$target = $par;
48
		} else {
49
			$target = $request->getVal( 'target' );
50
		}
51
52
		if ( $request->getVal( 'contribs' ) == 'newbie' || $par === 'newbies' ) {
53
			$target = 'newbies';
54
			$this->opts['contribs'] = 'newbie';
55
		} else {
56
			$this->opts['contribs'] = 'user';
57
		}
58
59
		$this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' );
60
61
		if ( !strlen( $target ) ) {
62
			if ( !$this->including() ) {
63
				$out->addHTML( $this->getForm() );
64
			}
65
66
			return;
67
		}
68
69
		$user = $this->getUser();
70
71
		$this->opts['limit'] = $request->getInt( 'limit', $user->getOption( 'rclimit' ) );
72
		$this->opts['target'] = $target;
73
		$this->opts['topOnly'] = $request->getBool( 'topOnly' );
74
		$this->opts['newOnly'] = $request->getBool( 'newOnly' );
75
76
		$nt = Title::makeTitleSafe( NS_USER, $target );
77
		if ( !$nt ) {
78
			$out->addHTML( $this->getForm() );
79
80
			return;
81
		}
82
		$userObj = User::newFromName( $nt->getText(), false );
83
		if ( !$userObj ) {
84
			$out->addHTML( $this->getForm() );
85
86
			return;
87
		}
88
		$id = $userObj->getId();
89
90
		if ( $this->opts['contribs'] != 'newbie' ) {
91
			$target = $nt->getText();
92
			$out->addSubtitle( $this->contributionsSub( $userObj ) );
93
			$out->setHTMLTitle( $this->msg(
94
				'pagetitle',
95
				$this->msg( 'contributions-title', $target )->plain()
96
			)->inContentLanguage() );
97
			$this->getSkin()->setRelevantUser( $userObj );
98
		} else {
99
			$out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) );
100
			$out->setHTMLTitle( $this->msg(
101
				'pagetitle',
102
				$this->msg( 'sp-contributions-newbies-title' )->plain()
103
			)->inContentLanguage() );
104
		}
105
106
		$ns = $request->getVal( 'namespace', null );
107
		if ( $ns !== null && $ns !== '' ) {
108
			$this->opts['namespace'] = intval( $ns );
109
		} else {
110
			$this->opts['namespace'] = '';
111
		}
112
113
		$this->opts['associated'] = $request->getBool( 'associated' );
114
		$this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' );
115
		$this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' );
116
117
		// Allows reverts to have the bot flag in recent changes. It is just here to
118
		// be passed in the form at the top of the page
119
		if ( $user->isAllowed( 'markbotedits' ) && $request->getBool( 'bot' ) ) {
120
			$this->opts['bot'] = '1';
121
		}
122
123
		$skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
124
		# Offset overrides year/month selection
125
		if ( $skip ) {
126
			$this->opts['year'] = '';
127
			$this->opts['month'] = '';
128
		} else {
129
			$this->opts['year'] = $request->getIntOrNull( 'year' );
130
			$this->opts['month'] = $request->getIntOrNull( 'month' );
131
		}
132
133
		$feedType = $request->getVal( 'feed' );
134
135
		$feedParams = [
136
			'action' => 'feedcontributions',
137
			'user' => $target,
138
		];
139
		if ( $this->opts['topOnly'] ) {
140
			$feedParams['toponly'] = true;
141
		}
142
		if ( $this->opts['newOnly'] ) {
143
			$feedParams['newonly'] = true;
144
		}
145
		if ( $this->opts['deletedOnly'] ) {
146
			$feedParams['deletedonly'] = true;
147
		}
148
		if ( $this->opts['tagfilter'] !== '' ) {
149
			$feedParams['tagfilter'] = $this->opts['tagfilter'];
150
		}
151
		if ( $this->opts['namespace'] !== '' ) {
152
			$feedParams['namespace'] = $this->opts['namespace'];
153
		}
154
		// Don't use year and month for the feed URL, but pass them on if
155
		// we redirect to API (if $feedType is specified)
156 View Code Duplication
		if ( $feedType && $this->opts['year'] !== null ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $feedType of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
157
			$feedParams['year'] = $this->opts['year'];
158
		}
159 View Code Duplication
		if ( $feedType && $this->opts['month'] !== null ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $feedType of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
160
			$feedParams['month'] = $this->opts['month'];
161
		}
162
163
		if ( $feedType ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $feedType of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
164
			// Maintain some level of backwards compatibility
165
			// If people request feeds using the old parameters, redirect to API
166
			$feedParams['feedformat'] = $feedType;
167
			$url = wfAppendQuery( wfScript( 'api' ), $feedParams );
168
169
			$out->redirect( $url, '301' );
170
171
			return;
172
		}
173
174
		// Add RSS/atom links
175
		$this->addFeedLinks( $feedParams );
176
177
		if ( Hooks::run( 'SpecialContributionsBeforeMainOutput', [ $id, $userObj, $this ] ) ) {
178
			if ( !$this->including() ) {
179
				$out->addHTML( $this->getForm() );
180
			}
181
			$pager = new ContribsPager( $this->getContext(), [
182
				'target' => $target,
183
				'contribs' => $this->opts['contribs'],
184
				'namespace' => $this->opts['namespace'],
185
				'tagfilter' => $this->opts['tagfilter'],
186
				'year' => $this->opts['year'],
187
				'month' => $this->opts['month'],
188
				'deletedOnly' => $this->opts['deletedOnly'],
189
				'topOnly' => $this->opts['topOnly'],
190
				'newOnly' => $this->opts['newOnly'],
191
				'nsInvert' => $this->opts['nsInvert'],
192
				'associated' => $this->opts['associated'],
193
			] );
194
195
			if ( !$pager->getNumRows() ) {
196
				$out->addWikiMsg( 'nocontribs', $target );
197
			} else {
198
				# Show a message about slave lag, if applicable
199
				$lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
200
				if ( $lag > 0 ) {
201
					$out->showLagWarning( $lag );
0 ignored issues
show
Bug introduced by
It seems like $lag defined by wfGetLB()->safeGetLag($pager->getDatabase()) on line 199 can also be of type boolean; however, OutputPage::showLagWarning() does only seem to accept integer, 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...
202
				}
203
204
				$output = $pager->getBody();
205
				if ( !$this->including() ) {
206
					$output = '<p>' . $pager->getNavigationBar() . '</p>' .
207
						$output .
208
						'<p>' . $pager->getNavigationBar() . '</p>';
209
				}
210
				$out->addHTML( $output );
211
			}
212
			$out->preventClickjacking( $pager->getPreventClickjacking() );
213
214
			# Show the appropriate "footer" message - WHOIS tools, etc.
215
			if ( $this->opts['contribs'] == 'newbie' ) {
216
				$message = 'sp-contributions-footer-newbies';
217
			} elseif ( IP::isIPAddress( $target ) ) {
218
				$message = 'sp-contributions-footer-anon';
219
			} elseif ( $userObj->isAnon() ) {
220
				// No message for non-existing users
221
				$message = '';
222
			} else {
223
				$message = 'sp-contributions-footer';
224
			}
225
226
			if ( $message ) {
227
				if ( !$this->including() ) {
228
					if ( !$this->msg( $message, $target )->isDisabled() ) {
229
						$out->wrapWikiMsg(
230
							"<div class='mw-contributions-footer'>\n$1\n</div>",
231
							[ $message, $target ] );
232
					}
233
				}
234
			}
235
		}
236
	}
237
238
	/**
239
	 * Generates the subheading with links
240
	 * @param User $userObj User object for the target
241
	 * @return string Appropriately-escaped HTML to be output literally
242
	 * @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php.
243
	 * Could be combined.
244
	 */
245
	protected function contributionsSub( $userObj ) {
246
		if ( $userObj->isAnon() ) {
247
			// Show a warning message that the user being searched for doesn't exists
248
			if ( !User::isIP( $userObj->getName() ) ) {
249
				$this->getOutput()->wrapWikiMsg(
250
					"<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
251
					[
252
						'contributions-userdoesnotexist',
253
						wfEscapeWikiText( $userObj->getName() ),
254
					]
255
				);
256
				if ( !$this->including() ) {
257
					$this->getOutput()->setStatusCode( 404 );
258
				}
259
			}
260
			$user = htmlspecialchars( $userObj->getName() );
261
		} else {
262
			$user = Linker::link( $userObj->getUserPage(), htmlspecialchars( $userObj->getName() ) );
263
		}
264
		$nt = $userObj->getUserPage();
265
		$talk = $userObj->getTalkPage();
266
		$links = '';
267
		if ( $talk ) {
268
			$tools = $this->getUserLinks( $nt, $talk, $userObj );
269
			$links = $this->getLanguage()->pipeList( $tools );
270
271
			// Show a note if the user is blocked and display the last block log entry.
272
			// Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
273
			// and also this will display a totally irrelevant log entry as a current block.
274
			if ( !$this->including() ) {
275
				$block = Block::newFromTarget( $userObj, $userObj );
276 View Code Duplication
				if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
277
					if ( $block->getType() == Block::TYPE_RANGE ) {
278
						$nt = MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget();
279
					}
280
281
					$out = $this->getOutput(); // showLogExtract() wants first parameter by reference
282
					LogEventsList::showLogExtract(
283
						$out,
284
						'block',
285
						$nt,
286
						'',
287
						[
288
							'lim' => 1,
289
							'showIfEmpty' => false,
290
							'msgKey' => [
291
								$userObj->isAnon() ?
292
									'sp-contributions-blocked-notice-anon' :
293
									'sp-contributions-blocked-notice',
294
								$userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
295
							],
296
							'offset' => '' # don't use WebRequest parameter offset
297
						]
298
					);
299
				}
300
			}
301
		}
302
303
		return $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() );
304
	}
305
306
	/**
307
	 * Links to different places.
308
	 * @param Title $userpage Target user page
309
	 * @param Title $talkpage Talk page
310
	 * @param User $target Target user object
311
	 * @return array
312
	 */
313
	public function getUserLinks( Title $userpage, Title $talkpage, User $target ) {
314
315
		$id = $target->getId();
316
		$username = $target->getName();
317
318
		$tools[] = Linker::link( $talkpage, $this->msg( 'sp-contributions-talk' )->escaped() );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tools was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tools = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
319
320
		if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) {
321
			if ( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
322
				if ( $target->isBlocked() && $target->getBlock()->getType() != Block::TYPE_AUTO ) {
323
					$tools[] = Linker::linkKnown( # Change block link
324
						SpecialPage::getTitleFor( 'Block', $username ),
325
						$this->msg( 'change-blocklink' )->escaped()
326
					);
327
					$tools[] = Linker::linkKnown( # Unblock link
328
						SpecialPage::getTitleFor( 'Unblock', $username ),
329
						$this->msg( 'unblocklink' )->escaped()
330
					);
331
				} else { # User is not blocked
332
					$tools[] = Linker::linkKnown( # Block link
333
						SpecialPage::getTitleFor( 'Block', $username ),
334
						$this->msg( 'blocklink' )->escaped()
335
					);
336
				}
337
			}
338
339
			# Block log link
340
			$tools[] = Linker::linkKnown(
341
				SpecialPage::getTitleFor( 'Log', 'block' ),
342
				$this->msg( 'sp-contributions-blocklog' )->escaped(),
343
				[],
344
				[ 'page' => $userpage->getPrefixedText() ]
345
			);
346
347
			# Suppression log link (bug 59120)
348 View Code Duplication
			if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
349
				$tools[] = Linker::linkKnown(
350
					SpecialPage::getTitleFor( 'Log', 'suppress' ),
351
					$this->msg( 'sp-contributions-suppresslog' )->escaped(),
352
					[],
353
					[ 'offender' => $username ]
354
				);
355
			}
356
		}
357
		# Uploads
358
		$tools[] = Linker::linkKnown(
359
			SpecialPage::getTitleFor( 'Listfiles', $username ),
360
			$this->msg( 'sp-contributions-uploads' )->escaped()
361
		);
362
363
		# Other logs link
364
		$tools[] = Linker::linkKnown(
365
			SpecialPage::getTitleFor( 'Log', $username ),
366
			$this->msg( 'sp-contributions-logs' )->escaped()
367
		);
368
369
		# Add link to deleted user contributions for priviledged users
370 View Code Duplication
		if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
371
			$tools[] = Linker::linkKnown(
372
				SpecialPage::getTitleFor( 'DeletedContributions', $username ),
373
				$this->msg( 'sp-contributions-deleted' )->escaped()
374
			);
375
		}
376
377
		# Add a link to change user rights for privileged users
378
		$userrightsPage = new UserrightsPage();
379
		$userrightsPage->setContext( $this->getContext() );
380 View Code Duplication
		if ( $userrightsPage->userCanChangeRights( $target ) ) {
381
			$tools[] = Linker::linkKnown(
382
				SpecialPage::getTitleFor( 'Userrights', $username ),
383
				$this->msg( 'sp-contributions-userrights' )->escaped()
384
			);
385
		}
386
387
		Hooks::run( 'ContributionsToolLinks', [ $id, $userpage, &$tools ] );
388
389
		return $tools;
390
	}
391
392
	/**
393
	 * Generates the namespace selector form with hidden attributes.
394
	 * @return string HTML fragment
395
	 */
396
	protected function getForm() {
397
		$this->opts['title'] = $this->getPageTitle()->getPrefixedText();
398
		if ( !isset( $this->opts['target'] ) ) {
399
			$this->opts['target'] = '';
400
		} else {
401
			$this->opts['target'] = str_replace( '_', ' ', $this->opts['target'] );
402
		}
403
404
		if ( !isset( $this->opts['namespace'] ) ) {
405
			$this->opts['namespace'] = '';
406
		}
407
408
		if ( !isset( $this->opts['nsInvert'] ) ) {
409
			$this->opts['nsInvert'] = '';
410
		}
411
412
		if ( !isset( $this->opts['associated'] ) ) {
413
			$this->opts['associated'] = false;
414
		}
415
416
		if ( !isset( $this->opts['contribs'] ) ) {
417
			$this->opts['contribs'] = 'user';
418
		}
419
420
		if ( !isset( $this->opts['year'] ) ) {
421
			$this->opts['year'] = '';
422
		}
423
424
		if ( !isset( $this->opts['month'] ) ) {
425
			$this->opts['month'] = '';
426
		}
427
428
		if ( $this->opts['contribs'] == 'newbie' ) {
429
			$this->opts['target'] = '';
430
		}
431
432
		if ( !isset( $this->opts['tagfilter'] ) ) {
433
			$this->opts['tagfilter'] = '';
434
		}
435
436
		if ( !isset( $this->opts['topOnly'] ) ) {
437
			$this->opts['topOnly'] = false;
438
		}
439
440
		if ( !isset( $this->opts['newOnly'] ) ) {
441
			$this->opts['newOnly'] = false;
442
		}
443
444
		$form = Html::openElement(
445
			'form',
446
			[
447
				'method' => 'get',
448
				'action' => wfScript(),
449
				'class' => 'mw-contributions-form'
450
			]
451
		);
452
453
		# Add hidden params for tracking except for parameters in $skipParameters
454
		$skipParameters = [
455
			'namespace',
456
			'nsInvert',
457
			'deletedOnly',
458
			'target',
459
			'contribs',
460
			'year',
461
			'month',
462
			'topOnly',
463
			'newOnly',
464
			'associated',
465
			'tagfilter'
466
		];
467
468
		foreach ( $this->opts as $name => $value ) {
469
			if ( in_array( $name, $skipParameters ) ) {
470
				continue;
471
			}
472
			$form .= "\t" . Html::hidden( $name, $value ) . "\n";
473
		}
474
475
		$tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
476
477
		if ( $tagFilter ) {
478
			$filterSelection = Html::rawElement(
479
				'td',
480
				[],
481
				implode( '&#160;', $tagFilter )
482
			);
483
		} else {
484
			$filterSelection = Html::rawElement( 'td', [ 'colspan' => 2 ], '' );
485
		}
486
487
		$this->getOutput()->addModules( 'mediawiki.userSuggest' );
488
489
		$labelNewbies = Xml::radioLabel(
490
			$this->msg( 'sp-contributions-newbies' )->text(),
491
			'contribs',
492
			'newbie',
493
			'newbie',
494
			$this->opts['contribs'] == 'newbie',
495
			[ 'class' => 'mw-input' ]
496
		);
497
		$labelUsername = Xml::radioLabel(
498
			$this->msg( 'sp-contributions-username' )->text(),
499
			'contribs',
500
			'user',
501
			'user',
502
			$this->opts['contribs'] == 'user',
503
			[ 'class' => 'mw-input' ]
504
		);
505
		$input = Html::input(
506
			'target',
507
			$this->opts['target'],
508
			'text',
509
			[
510
				'size' => '40',
511
				'required' => '',
512
				'class' => [
513
					'mw-input',
514
					'mw-ui-input-inline',
515
					'mw-autocomplete-user', // used by mediawiki.userSuggest
516
				],
517
			] + (
518
				// Only autofocus if target hasn't been specified or in non-newbies mode
519
				( $this->opts['contribs'] === 'newbie' || $this->opts['target'] )
520
					? [] : [ 'autofocus' => true ]
521
				)
522
		);
523
524
		$targetSelection = Html::rawElement(
525
			'td',
526
			[ 'colspan' => 2 ],
527
			$labelNewbies . '<br />' . $labelUsername . ' ' . $input . ' '
528
		);
529
530
		$namespaceSelection = Xml::tags(
531
			'td',
532
			[],
533
			Xml::label(
534
				$this->msg( 'namespace' )->text(),
535
				'namespace',
536
				''
537
			) .
538
			Html::namespaceSelector(
539
				[ 'selected' => $this->opts['namespace'], 'all' => '' ],
540
				[
541
					'name' => 'namespace',
542
					'id' => 'namespace',
543
					'class' => 'namespaceselector',
544
				]
545
			) . '&#160;' .
546
				Html::rawElement(
547
					'span',
548
					[ 'class' => 'mw-input-with-label' ],
549
					Xml::checkLabel(
550
						$this->msg( 'invert' )->text(),
551
						'nsInvert',
552
						'nsInvert',
553
						$this->opts['nsInvert'],
554
						[
555
							'title' => $this->msg( 'tooltip-invert' )->text(),
556
							'class' => 'mw-input'
557
						]
558
					) . '&#160;'
559
				) .
560
				Html::rawElement( 'span', [ 'class' => 'mw-input-with-label' ],
561
					Xml::checkLabel(
562
						$this->msg( 'namespace_association' )->text(),
563
						'associated',
564
						'associated',
565
						$this->opts['associated'],
566
						[
567
							'title' => $this->msg( 'tooltip-namespace_association' )->text(),
568
							'class' => 'mw-input'
569
						]
570
					) . '&#160;'
571
				)
572
		);
573
574
		$filters = [];
575
576
		if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
577
			$filters[] = Html::rawElement(
578
				'span',
579
				[ 'class' => 'mw-input-with-label' ],
580
				Xml::checkLabel(
581
					$this->msg( 'history-show-deleted' )->text(),
582
					'deletedOnly',
583
					'mw-show-deleted-only',
584
					$this->opts['deletedOnly'],
585
					[ 'class' => 'mw-input' ]
586
				)
587
			);
588
		}
589
590
		$filters[] = Html::rawElement(
591
			'span',
592
			[ 'class' => 'mw-input-with-label' ],
593
			Xml::checkLabel(
594
				$this->msg( 'sp-contributions-toponly' )->text(),
595
				'topOnly',
596
				'mw-show-top-only',
597
				$this->opts['topOnly'],
598
				[ 'class' => 'mw-input' ]
599
			)
600
		);
601
		$filters[] = Html::rawElement(
602
			'span',
603
			[ 'class' => 'mw-input-with-label' ],
604
			Xml::checkLabel(
605
				$this->msg( 'sp-contributions-newonly' )->text(),
606
				'newOnly',
607
				'mw-show-new-only',
608
				$this->opts['newOnly'],
609
				[ 'class' => 'mw-input' ]
610
			)
611
		);
612
613
		Hooks::run(
614
			'SpecialContributions::getForm::filters',
615
			[ $this, &$filters ]
616
		);
617
618
		$extraOptions = Html::rawElement(
619
			'td',
620
			[ 'colspan' => 2 ],
621
			implode( '', $filters )
622
		);
623
624
		$dateSelectionAndSubmit = Xml::tags( 'td', [ 'colspan' => 2 ],
625
			Xml::dateMenu(
626
				$this->opts['year'] === '' ? MWTimestamp::getInstance()->format( 'Y' ) : $this->opts['year'],
627
				$this->opts['month']
628
			) . ' ' .
629
				Html::submitButton(
630
					$this->msg( 'sp-contributions-submit' )->text(),
631
					[ 'class' => 'mw-submit' ], [ 'mw-ui-progressive' ]
632
				)
633
		);
634
635
		$form .= Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() );
636
		$form .= Html::rawElement( 'table', [ 'class' => 'mw-contributions-table' ], "\n" .
637
			Html::rawElement( 'tr', [], $targetSelection ) . "\n" .
638
			Html::rawElement( 'tr', [], $namespaceSelection ) . "\n" .
639
			Html::rawElement( 'tr', [], $filterSelection ) . "\n" .
640
			Html::rawElement( 'tr', [], $extraOptions ) . "\n" .
641
			Html::rawElement( 'tr', [], $dateSelectionAndSubmit ) . "\n"
642
		);
643
644
		$explain = $this->msg( 'sp-contributions-explain' );
645
		if ( !$explain->isBlank() ) {
646
			$form .= "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>";
647
		}
648
649
		$form .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' );
650
651
		return $form;
652
	}
653
654
	/**
655
	 * Return an array of subpages beginning with $search that this special page will accept.
656
	 *
657
	 * @param string $search Prefix to search for
658
	 * @param int $limit Maximum number of results to return (usually 10)
659
	 * @param int $offset Number of results to skip (usually 0)
660
	 * @return string[] Matching subpages
661
	 */
662 View Code Duplication
	public function prefixSearchSubpages( $search, $limit, $offset ) {
663
		$user = User::newFromName( $search );
664
		if ( !$user ) {
665
			// No prefix suggestion for invalid user
666
			return [];
667
		}
668
		// Autocomplete subpage as user list - public to allow caching
669
		return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
670
	}
671
672
	protected function getGroupName() {
673
		return 'users';
674
	}
675
}
676