Completed
Branch master (9259dd)
by
unknown
27:26
created

SpecialBlock::processForm()   F

Complexity

Conditions 38
Paths 7940

Size

Total Lines 214
Code Lines 111

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 38
eloc 111
nc 7940
nop 2
dl 0
loc 214
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:Block
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
 * A special page that allows users with 'block' right to block users from
26
 * editing pages and other actions
27
 *
28
 * @ingroup SpecialPage
29
 */
30
class SpecialBlock extends FormSpecialPage {
31
	/** @var User|string|null User to be blocked, as passed either by parameter (url?wpTarget=Foo)
32
	 * or as subpage (Special:Block/Foo) */
33
	protected $target;
34
35
	/** @var int Block::TYPE_ constant */
36
	protected $type;
37
38
	/** @var User|string The previous block target */
39
	protected $previousTarget;
40
41
	/** @var bool Whether the previous submission of the form asked for HideUser */
42
	protected $requestedHideUser;
43
44
	/** @var bool */
45
	protected $alreadyBlocked;
46
47
	/** @var array */
48
	protected $preErrors = [];
49
50
	public function __construct() {
51
		parent::__construct( 'Block', 'block' );
52
	}
53
54
	public function doesWrites() {
55
		return true;
56
	}
57
58
	/**
59
	 * Checks that the user can unblock themselves if they are trying to do so
60
	 *
61
	 * @param User $user
62
	 * @throws ErrorPageError
63
	 */
64
	protected function checkExecutePermissions( User $user ) {
65
		parent::checkExecutePermissions( $user );
66
67
		# bug 15810: blocked admins should have limited access here
68
		$status = self::checkUnblockSelf( $this->target, $user );
0 ignored issues
show
Bug introduced by
It seems like $this->target can also be of type null; however, SpecialBlock::checkUnblockSelf() does only seem to accept object<User>|integer|string, 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...
69
		if ( $status !== true ) {
70
			throw new ErrorPageError( 'badaccess', $status );
0 ignored issues
show
Bug introduced by
It seems like $status defined by self::checkUnblockSelf($this->target, $user) on line 68 can also be of type boolean; however, ErrorPageError::__construct() does only seem to accept string|object<Message>, 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...
71
		}
72
	}
73
74
	/**
75
	 * Handle some magic here
76
	 *
77
	 * @param string $par
78
	 */
79
	protected function setParameter( $par ) {
80
		# Extract variables from the request.  Try not to get into a situation where we
81
		# need to extract *every* variable from the form just for processing here, but
82
		# there are legitimate uses for some variables
83
		$request = $this->getRequest();
84
		list( $this->target, $this->type ) = self::getTargetAndType( $par, $request );
85
		if ( $this->target instanceof User ) {
86
			# Set the 'relevant user' in the skin, so it displays links like Contributions,
87
			# User logs, UserRights, etc.
88
			$this->getSkin()->setRelevantUser( $this->target );
89
		}
90
91
		list( $this->previousTarget, /*...*/ ) =
92
			Block::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
93
		$this->requestedHideUser = $request->getBool( 'wpHideUser' );
94
	}
95
96
	/**
97
	 * Customizes the HTMLForm a bit
98
	 *
99
	 * @param HTMLForm $form
100
	 */
101
	protected function alterForm( HTMLForm $form ) {
102
		$form->setWrapperLegendMsg( 'blockip-legend' );
103
		$form->setHeaderText( '' );
104
		$form->setSubmitDestructive();
105
106
		$msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
107
		$form->setSubmitTextMsg( $msg );
108
109
		$this->addHelpLink( 'Help:Blocking users' );
110
111
		# Don't need to do anything if the form has been posted
112
		if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
113
			$s = $form->formatErrors( $this->preErrors );
114
			if ( $s ) {
115
				$form->addHeaderText( Html::rawElement(
116
					'div',
117
					[ 'class' => 'error' ],
118
					$s
119
				) );
120
			}
121
		}
122
	}
123
124
	/**
125
	 * Get the HTMLForm descriptor array for the block form
126
	 * @return array
127
	 */
128
	protected function getFormFields() {
129
		global $wgBlockAllowsUTEdit;
130
131
		$user = $this->getUser();
132
133
		$suggestedDurations = self::getSuggestedDurations();
134
135
		$a = [
136
			'Target' => [
137
				'type' => 'text',
138
				'label-message' => 'ipaddressorusername',
139
				'id' => 'mw-bi-target',
140
				'size' => '45',
141
				'autofocus' => true,
142
				'required' => true,
143
				'validation-callback' => [ __CLASS__, 'validateTargetField' ],
144
				'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
145
			],
146
			'Expiry' => [
147
				'type' => !count( $suggestedDurations ) ? 'text' : 'selectorother',
148
				'label-message' => 'ipbexpiry',
149
				'required' => true,
150
				'options' => $suggestedDurations,
151
				'other' => $this->msg( 'ipbother' )->text(),
152
				'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
153
			],
154
			'Reason' => [
155
				'type' => 'selectandother',
156
				'maxlength' => 255,
157
				'label-message' => 'ipbreason',
158
				'options-message' => 'ipbreason-dropdown',
159
			],
160
			'CreateAccount' => [
161
				'type' => 'check',
162
				'label-message' => 'ipbcreateaccount',
163
				'default' => true,
164
			],
165
		];
166
167
		if ( self::canBlockEmail( $user ) ) {
168
			$a['DisableEmail'] = [
169
				'type' => 'check',
170
				'label-message' => 'ipbemailban',
171
			];
172
		}
173
174
		if ( $wgBlockAllowsUTEdit ) {
175
			$a['DisableUTEdit'] = [
176
				'type' => 'check',
177
				'label-message' => 'ipb-disableusertalk',
178
				'default' => false,
179
			];
180
		}
181
182
		$a['AutoBlock'] = [
183
			'type' => 'check',
184
			'label-message' => 'ipbenableautoblock',
185
			'default' => true,
186
		];
187
188
		# Allow some users to hide name from block log, blocklist and listusers
189
		if ( $user->isAllowed( 'hideuser' ) ) {
190
			$a['HideUser'] = [
191
				'type' => 'check',
192
				'label-message' => 'ipbhidename',
193
				'cssclass' => 'mw-block-hideuser',
194
			];
195
		}
196
197
		# Watchlist their user page? (Only if user is logged in)
198
		if ( $user->isLoggedIn() ) {
199
			$a['Watch'] = [
200
				'type' => 'check',
201
				'label-message' => 'ipbwatchuser',
202
			];
203
		}
204
205
		$a['HardBlock'] = [
206
			'type' => 'check',
207
			'label-message' => 'ipb-hardblock',
208
			'default' => false,
209
		];
210
211
		# This is basically a copy of the Target field, but the user can't change it, so we
212
		# can see if the warnings we maybe showed to the user before still apply
213
		$a['PreviousTarget'] = [
214
			'type' => 'hidden',
215
			'default' => false,
216
		];
217
218
		# We'll turn this into a checkbox if we need to
219
		$a['Confirm'] = [
220
			'type' => 'hidden',
221
			'default' => '',
222
			'label-message' => 'ipb-confirm',
223
		];
224
225
		$this->maybeAlterFormDefaults( $a );
226
227
		// Allow extensions to add more fields
228
		Hooks::run( 'SpecialBlockModifyFormFields', [ $this, &$a ] );
229
230
		return $a;
231
	}
232
233
	/**
234
	 * If the user has already been blocked with similar settings, load that block
235
	 * and change the defaults for the form fields to match the existing settings.
236
	 * @param array $fields HTMLForm descriptor array
237
	 * @return bool Whether fields were altered (that is, whether the target is
238
	 *     already blocked)
239
	 */
240
	protected function maybeAlterFormDefaults( &$fields ) {
241
		# This will be overwritten by request data
242
		$fields['Target']['default'] = (string)$this->target;
243
244
		if ( $this->target ) {
245
			$status = self::validateTarget( $this->target, $this->getUser() );
0 ignored issues
show
Bug introduced by
It seems like $this->target can also be of type object<User>; however, SpecialBlock::validateTarget() does only seem to accept string, 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...
246
			if ( !$status->isOK() ) {
247
				$errors = $status->getErrorsArray();
0 ignored issues
show
Deprecated Code introduced by
The method Status::getErrorsArray() has been deprecated with message: 1.25

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...
248
				$this->preErrors = array_merge( $this->preErrors, $errors );
249
			}
250
		}
251
252
		# This won't be
253
		$fields['PreviousTarget']['default'] = (string)$this->target;
254
255
		$block = Block::newFromTarget( $this->target );
0 ignored issues
show
Bug introduced by
It seems like $this->target can also be of type null; however, Block::newFromTarget() does only seem to accept string|object<User>|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...
256
257
		if ( $block instanceof Block && !$block->mAuto # The block exists and isn't an autoblock
258
			&& ( $this->type != Block::TYPE_RANGE # The block isn't a rangeblock
259
				|| $block->getTarget() == $this->target ) # or if it is, the range is what we're about to block
260
		) {
261
			$fields['HardBlock']['default'] = $block->isHardblock();
262
			$fields['CreateAccount']['default'] = $block->prevents( 'createaccount' );
263
			$fields['AutoBlock']['default'] = $block->isAutoblocking();
264
265
			if ( isset( $fields['DisableEmail'] ) ) {
266
				$fields['DisableEmail']['default'] = $block->prevents( 'sendemail' );
267
			}
268
269
			if ( isset( $fields['HideUser'] ) ) {
270
				$fields['HideUser']['default'] = $block->mHideName;
271
			}
272
273
			if ( isset( $fields['DisableUTEdit'] ) ) {
274
				$fields['DisableUTEdit']['default'] = $block->prevents( 'editownusertalk' );
275
			}
276
277
			// If the username was hidden (ipb_deleted == 1), don't show the reason
278
			// unless this user also has rights to hideuser: Bug 35839
279
			if ( !$block->mHideName || $this->getUser()->isAllowed( 'hideuser' ) ) {
280
				$fields['Reason']['default'] = $block->mReason;
281
			} else {
282
				$fields['Reason']['default'] = '';
283
			}
284
285
			if ( $this->getRequest()->wasPosted() ) {
286
				# Ok, so we got a POST submission asking us to reblock a user.  So show the
287
				# confirm checkbox; the user will only see it if they haven't previously
288
				$fields['Confirm']['type'] = 'check';
289
			} else {
290
				# We got a target, but it wasn't a POST request, so the user must have gone
291
				# to a link like [[Special:Block/User]].  We don't need to show the checkbox
292
				# as long as they go ahead and block *that* user
293
				$fields['Confirm']['default'] = 1;
294
			}
295
296
			if ( $block->mExpiry == 'infinity' ) {
297
				$fields['Expiry']['default'] = 'infinite';
298
			} else {
299
				$fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->mExpiry );
300
			}
301
302
			$this->alreadyBlocked = true;
303
			$this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( (string)$block->getTarget() ) ];
304
		}
305
306
		# We always need confirmation to do HideUser
307 View Code Duplication
		if ( $this->requestedHideUser ) {
308
			$fields['Confirm']['type'] = 'check';
309
			unset( $fields['Confirm']['default'] );
310
			$this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
311
		}
312
313
		# Or if the user is trying to block themselves
314 View Code Duplication
		if ( (string)$this->target === $this->getUser()->getName() ) {
315
			$fields['Confirm']['type'] = 'check';
316
			unset( $fields['Confirm']['default'] );
317
			$this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
318
		}
319
	}
320
321
	/**
322
	 * Add header elements like block log entries, etc.
323
	 * @return string
324
	 */
325
	protected function preText() {
326
		$this->getOutput()->addModules( [ 'mediawiki.special.block', 'mediawiki.userSuggest' ] );
327
328
		$blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
329
		$text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
330
331
		$otherBlockMessages = [];
332
		if ( $this->target !== null ) {
333
			$targetName = $this->target;
334
			if ( $this->target instanceof User ) {
335
				$targetName = $this->target->getName();
336
			}
337
			# Get other blocks, i.e. from GlobalBlocking or TorBlock extension
338
			Hooks::run( 'OtherBlockLogLink', [ &$otherBlockMessages, $targetName ] );
339
340
			if ( count( $otherBlockMessages ) ) {
341
				$s = Html::rawElement(
342
					'h2',
343
					[],
344
					$this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
345
				) . "\n";
346
347
				$list = '';
348
349
				foreach ( $otherBlockMessages as $link ) {
350
					$list .= Html::rawElement( 'li', [], $link ) . "\n";
351
				}
352
353
				$s .= Html::rawElement(
354
					'ul',
355
					[ 'class' => 'mw-blockip-alreadyblocked' ],
356
					$list
357
				) . "\n";
358
359
				$text .= $s;
360
			}
361
		}
362
363
		return $text;
364
	}
365
366
	/**
367
	 * Add footer elements to the form
368
	 * @return string
369
	 */
370
	protected function postText() {
371
		$links = [];
372
373
		$this->getOutput()->addModuleStyles( 'mediawiki.special' );
374
375
		# Link to the user's contributions, if applicable
376
		if ( $this->target instanceof User ) {
377
			$contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
378
			$links[] = Linker::link(
379
				$contribsPage,
380
				$this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->escaped()
381
			);
382
		}
383
384
		# Link to unblock the specified user, or to a blank unblock form
385
		if ( $this->target instanceof User ) {
386
			$message = $this->msg(
387
				'ipb-unblock-addr',
388
				wfEscapeWikiText( $this->target->getName() )
389
			)->parse();
390
			$list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
391
		} else {
392
			$message = $this->msg( 'ipb-unblock' )->parse();
393
			$list = SpecialPage::getTitleFor( 'Unblock' );
394
		}
395
		$links[] = Linker::linkKnown( $list, $message, [] );
396
397
		# Link to the block list
398
		$links[] = Linker::linkKnown(
399
			SpecialPage::getTitleFor( 'BlockList' ),
400
			$this->msg( 'ipb-blocklist' )->escaped()
401
		);
402
403
		$user = $this->getUser();
404
405
		# Link to edit the block dropdown reasons, if applicable
406
		if ( $user->isAllowed( 'editinterface' ) ) {
407
			$links[] = Linker::linkKnown(
408
				$this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
409
				$this->msg( 'ipb-edit-dropdown' )->escaped(),
410
				[],
411
				[ 'action' => 'edit' ]
412
			);
413
		}
414
415
		$text = Html::rawElement(
416
			'p',
417
			[ 'class' => 'mw-ipb-conveniencelinks' ],
418
			$this->getLanguage()->pipeList( $links )
419
		);
420
421
		$userTitle = self::getTargetUserTitle( $this->target );
0 ignored issues
show
Bug introduced by
It seems like $this->target can also be of type null; however, SpecialBlock::getTargetUserTitle() does only seem to accept object<User>|string, 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...
422
		if ( $userTitle ) {
423
			# Get relevant extracts from the block and suppression logs, if possible
424
			$out = '';
425
426
			LogEventsList::showLogExtract(
427
				$out,
428
				'block',
429
				$userTitle,
430
				'',
431
				[
432
					'lim' => 10,
433
					'msgKey' => [ 'blocklog-showlog', $userTitle->getText() ],
434
					'showIfEmpty' => false
435
				]
436
			);
437
			$text .= $out;
438
439
			# Add suppression block entries if allowed
440
			if ( $user->isAllowed( 'suppressionlog' ) ) {
441
				LogEventsList::showLogExtract(
442
					$out,
443
					'suppress',
444
					$userTitle,
445
					'',
446
					[
447
						'lim' => 10,
448
						'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
449
						'msgKey' => [ 'blocklog-showsuppresslog', $userTitle->getText() ],
450
						'showIfEmpty' => false
451
					]
452
				);
453
454
				$text .= $out;
455
			}
456
		}
457
458
		return $text;
459
	}
460
461
	/**
462
	 * Get a user page target for things like logs.
463
	 * This handles account and IP range targets.
464
	 * @param User|string $target
465
	 * @return Title|null
466
	 */
467
	protected static function getTargetUserTitle( $target ) {
468
		if ( $target instanceof User ) {
469
			return $target->getUserPage();
470
		} elseif ( IP::isIPAddress( $target ) ) {
471
			return Title::makeTitleSafe( NS_USER, $target );
472
		}
473
474
		return null;
475
	}
476
477
	/**
478
	 * Determine the target of the block, and the type of target
479
	 * @todo Should be in Block.php?
480
	 * @param string $par Subpage parameter passed to setup, or data value from
481
	 *     the HTMLForm
482
	 * @param WebRequest $request Optionally try and get data from a request too
483
	 * @return array( User|string|null, Block::TYPE_ constant|null )
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
484
	 */
485
	public static function getTargetAndType( $par, WebRequest $request = null ) {
486
		$i = 0;
487
		$target = null;
488
489
		while ( true ) {
490
			switch ( $i++ ) {
491
				case 0:
492
					# The HTMLForm will check wpTarget first and only if it doesn't get
493
					# a value use the default, which will be generated from the options
494
					# below; so this has to have a higher precedence here than $par, or
495
					# we could end up with different values in $this->target and the HTMLForm!
496
					if ( $request instanceof WebRequest ) {
497
						$target = $request->getText( 'wpTarget', null );
498
					}
499
					break;
500
				case 1:
501
					$target = $par;
502
					break;
503
				case 2:
504
					if ( $request instanceof WebRequest ) {
505
						$target = $request->getText( 'ip', null );
506
					}
507
					break;
508
				case 3:
509
					# B/C @since 1.18
510
					if ( $request instanceof WebRequest ) {
511
						$target = $request->getText( 'wpBlockAddress', null );
512
					}
513
					break;
514
				case 4:
515
					break 2;
516
			}
517
518
			list( $target, $type ) = Block::parseTarget( $target );
519
520
			if ( $type !== null ) {
521
				return [ $target, $type ];
522
			}
523
		}
524
525
		return [ null, null ];
526
	}
527
528
	/**
529
	 * HTMLForm field validation-callback for Target field.
530
	 * @since 1.18
531
	 * @param string $value
532
	 * @param array $alldata
533
	 * @param HTMLForm $form
534
	 * @return Message
535
	 */
536
	public static function validateTargetField( $value, $alldata, $form ) {
537
		$status = self::validateTarget( $value, $form->getUser() );
538
		if ( !$status->isOK() ) {
539
			$errors = $status->getErrorsArray();
0 ignored issues
show
Deprecated Code introduced by
The method Status::getErrorsArray() has been deprecated with message: 1.25

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...
540
541
			return call_user_func_array( [ $form, 'msg' ], $errors[0] );
542
		} else {
543
			return true;
544
		}
545
	}
546
547
	/**
548
	 * Validate a block target.
549
	 *
550
	 * @since 1.21
551
	 * @param string $value Block target to check
552
	 * @param User $user Performer of the block
553
	 * @return Status
554
	 */
555
	public static function validateTarget( $value, User $user ) {
556
		global $wgBlockCIDRLimit;
557
558
		/** @var User $target */
559
		list( $target, $type ) = self::getTargetAndType( $value );
560
		$status = Status::newGood( $target );
561
562
		if ( $type == Block::TYPE_USER ) {
563
			if ( $target->isAnon() ) {
564
				$status->fatal(
565
					'nosuchusershort',
566
					wfEscapeWikiText( $target->getName() )
567
				);
568
			}
569
570
			$unblockStatus = self::checkUnblockSelf( $target, $user );
571
			if ( $unblockStatus !== true ) {
572
				$status->fatal( 'badaccess', $unblockStatus );
573
			}
574
		} elseif ( $type == Block::TYPE_RANGE ) {
575
			list( $ip, $range ) = explode( '/', $target, 2 );
576
577
			if (
578
				( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 ) ||
579
				( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 )
580
			) {
581
				// Range block effectively disabled
582
				$status->fatal( 'range_block_disabled' );
583
			}
584
585
			if (
586
				( IP::isIPv4( $ip ) && $range > 32 ) ||
587
				( IP::isIPv6( $ip ) && $range > 128 )
588
			) {
589
				// Dodgy range
590
				$status->fatal( 'ip_range_invalid' );
591
			}
592
593 View Code Duplication
			if ( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) {
594
				$status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
595
			}
596
597 View Code Duplication
			if ( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) {
598
				$status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
599
			}
600
		} elseif ( $type == Block::TYPE_IP ) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
601
			# All is well
602
		} else {
603
			$status->fatal( 'badipaddress' );
604
		}
605
606
		return $status;
607
	}
608
609
	/**
610
	 * Given the form data, actually implement a block. This is also called from ApiBlock.
611
	 *
612
	 * @param array $data
613
	 * @param IContextSource $context
614
	 * @return bool|string
615
	 */
616
	public static function processForm( array $data, IContextSource $context ) {
617
		global $wgBlockAllowsUTEdit, $wgHideUserContribLimit, $wgContLang;
618
619
		$performer = $context->getUser();
620
621
		// Handled by field validator callback
622
		// self::validateTargetField( $data['Target'] );
623
624
		# This might have been a hidden field or a checkbox, so interesting data
625
		# can come from it
626
		$data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
627
628
		/** @var User $target */
629
		list( $target, $type ) = self::getTargetAndType( $data['Target'] );
630
		if ( $type == Block::TYPE_USER ) {
631
			$user = $target;
632
			$target = $user->getName();
633
			$userId = $user->getId();
634
635
			# Give admins a heads-up before they go and block themselves.  Much messier
636
			# to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
637
			# permission anyway, although the code does allow for it.
638
			# Note: Important to use $target instead of $data['Target']
639
			# since both $data['PreviousTarget'] and $target are normalized
640
			# but $data['target'] gets overridden by (non-normalized) request variable
641
			# from previous request.
642
			if ( $target === $performer->getName() &&
643
				( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
644
			) {
645
				return [ 'ipb-blockingself', 'ipb-confirmaction' ];
646
			}
647
		} elseif ( $type == Block::TYPE_RANGE ) {
648
			$userId = 0;
649
		} elseif ( $type == Block::TYPE_IP ) {
650
			$target = $target->getName();
651
			$userId = 0;
652
		} else {
653
			# This should have been caught in the form field validation
654
			return [ 'badipaddress' ];
655
		}
656
657
		$expiryTime = self::parseExpiryInput( $data['Expiry'] );
658
659
		if (
660
			// an expiry time is needed
661
			( strlen( $data['Expiry'] ) == 0 ) ||
662
			// can't be a larger string as 50 (it should be a time format in any way)
663
			( strlen( $data['Expiry'] ) > 50 ) ||
664
			// check, if the time could be parsed
665
			!$expiryTime
0 ignored issues
show
Bug Best Practice introduced by
The expression $expiryTime of type false|string is loosely compared to false; 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...
666
		) {
667
			return [ 'ipb_expiry_invalid' ];
668
		}
669
670
		// an expiry time should be in the future, not in the
671
		// past (wouldn't make any sense) - bug T123069
672
		if ( $expiryTime < wfTimestampNow() ) {
673
			return [ 'ipb_expiry_old' ];
674
		}
675
676
		if ( !isset( $data['DisableEmail'] ) ) {
677
			$data['DisableEmail'] = false;
678
		}
679
680
		# If the user has done the form 'properly', they won't even have been given the
681
		# option to suppress-block unless they have the 'hideuser' permission
682
		if ( !isset( $data['HideUser'] ) ) {
683
			$data['HideUser'] = false;
684
		}
685
686
		if ( $data['HideUser'] ) {
687
			if ( !$performer->isAllowed( 'hideuser' ) ) {
688
				# this codepath is unreachable except by a malicious user spoofing forms,
689
				# or by race conditions (user has hideuser and block rights, loads block form,
690
				# and loses hideuser rights before submission); so need to fail completely
691
				# rather than just silently disable hiding
692
				return [ 'badaccess-group0' ];
693
			}
694
695
			# Recheck params here...
696
			if ( $type != Block::TYPE_USER ) {
697
				$data['HideUser'] = false; # IP users should not be hidden
698
			} elseif ( !wfIsInfinity( $data['Expiry'] ) ) {
699
				# Bad expiry.
700
				return [ 'ipb_expiry_temp' ];
701
			} elseif ( $wgHideUserContribLimit !== false
702
				&& $user->getEditCount() > $wgHideUserContribLimit
0 ignored issues
show
Bug introduced by
The variable $user 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...
703
			) {
704
				# Typically, the user should have a handful of edits.
705
				# Disallow hiding users with many edits for performance.
706
				return [ [ 'ipb_hide_invalid',
707
					Message::numParam( $wgHideUserContribLimit ) ] ];
708
			} elseif ( !$data['Confirm'] ) {
709
				return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
710
			}
711
		}
712
713
		# Create block object.
714
		$block = new Block();
715
		$block->setTarget( $target );
716
		$block->setBlocker( $performer );
717
		# Truncate reason for whole multibyte characters
718
		$block->mReason = $wgContLang->truncate( $data['Reason'][0], 255 );
719
		$block->mExpiry = $expiryTime;
720
		$block->prevents( 'createaccount', $data['CreateAccount'] );
721
		$block->prevents( 'editownusertalk', ( !$wgBlockAllowsUTEdit || $data['DisableUTEdit'] ) );
722
		$block->prevents( 'sendemail', $data['DisableEmail'] );
723
		$block->isHardblock( $data['HardBlock'] );
724
		$block->isAutoblocking( $data['AutoBlock'] );
725
		$block->mHideName = $data['HideUser'];
726
727
		$reason = [ 'hookaborted' ];
728
		if ( !Hooks::run( 'BlockIp', [ &$block, &$performer, &$reason ] ) ) {
729
			return $reason;
730
		}
731
732
		# Try to insert block. Is there a conflicting block?
733
		$status = $block->insert();
734
		if ( !$status ) {
735
			# Indicates whether the user is confirming the block and is aware of
736
			# the conflict (did not change the block target in the meantime)
737
			$blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
738
				&& $data['PreviousTarget'] !== $target );
739
740
			# Special case for API - bug 32434
741
			$reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
742
743
			# Show form unless the user is already aware of this...
744
			if ( $blockNotConfirmed || $reblockNotAllowed ) {
745
				return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
746
				# Otherwise, try to update the block...
747
			} else {
748
				# This returns direct blocks before autoblocks/rangeblocks, since we should
749
				# be sure the user is blocked by now it should work for our purposes
750
				$currentBlock = Block::newFromTarget( $target );
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type null; however, Block::newFromTarget() does only seem to accept string|object<User>|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...
751
752
				if ( $block->equals( $currentBlock ) ) {
0 ignored issues
show
Bug introduced by
It seems like $currentBlock defined by \Block::newFromTarget($target) on line 750 can be null; however, Block::equals() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
753
					return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
754
				}
755
756
				# If the name was hidden and the blocking user cannot hide
757
				# names, then don't allow any block changes...
758
				if ( $currentBlock->mHideName && !$performer->isAllowed( 'hideuser' ) ) {
759
					return [ 'cant-see-hidden-user' ];
760
				}
761
762
				$currentBlock->isHardblock( $block->isHardblock() );
763
				$currentBlock->prevents( 'createaccount', $block->prevents( 'createaccount' ) );
764
				$currentBlock->mExpiry = $block->mExpiry;
765
				$currentBlock->isAutoblocking( $block->isAutoblocking() );
766
				$currentBlock->mHideName = $block->mHideName;
767
				$currentBlock->prevents( 'sendemail', $block->prevents( 'sendemail' ) );
768
				$currentBlock->prevents( 'editownusertalk', $block->prevents( 'editownusertalk' ) );
769
				$currentBlock->mReason = $block->mReason;
770
771
				$status = $currentBlock->update();
772
773
				$logaction = 'reblock';
774
775
				# Unset _deleted fields if requested
776
				if ( $currentBlock->mHideName && !$data['HideUser'] ) {
777
					RevisionDeleteUser::unsuppressUserName( $target, $userId );
778
				}
779
780
				# If hiding/unhiding a name, this should go in the private logs
781
				if ( (bool)$currentBlock->mHideName ) {
782
					$data['HideUser'] = true;
783
				}
784
			}
785
		} else {
786
			$logaction = 'block';
787
		}
788
789
		Hooks::run( 'BlockIpComplete', [ $block, $performer ] );
790
791
		# Set *_deleted fields if requested
792
		if ( $data['HideUser'] ) {
793
			RevisionDeleteUser::suppressUserName( $target, $userId );
794
		}
795
796
		# Can't watch a rangeblock
797
		if ( $type != Block::TYPE_RANGE && $data['Watch'] ) {
798
			WatchAction::doWatch(
799
				Title::makeTitle( NS_USER, $target ),
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type null or object<User>; however, Title::makeTitle() does only seem to accept string, 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...
800
				$performer,
801
				User::IGNORE_USER_RIGHTS
802
			);
803
		}
804
805
		# Block constructor sanitizes certain block options on insert
806
		$data['BlockEmail'] = $block->prevents( 'sendemail' );
807
		$data['AutoBlock'] = $block->isAutoblocking();
808
809
		# Prepare log parameters
810
		$logParams = [];
811
		$logParams['5::duration'] = $data['Expiry'];
812
		$logParams['6::flags'] = self::blockLogFlags( $data, $type );
813
814
		# Make log entry, if the name is hidden, put it in the suppression log
815
		$log_type = $data['HideUser'] ? 'suppress' : 'block';
816
		$logEntry = new ManualLogEntry( $log_type, $logaction );
817
		$logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type null or object<User>; however, Title::makeTitle() does only seem to accept string, 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...
818
		$logEntry->setComment( $data['Reason'][0] );
819
		$logEntry->setPerformer( $performer );
820
		$logEntry->setParameters( $logParams );
821
		# Relate log ID to block IDs (bug 25763)
822
		$blockIds = array_merge( [ $status['id'] ], $status['autoIds'] );
823
		$logEntry->setRelations( [ 'ipb_id' => $blockIds ] );
824
		$logId = $logEntry->insert();
825
		$logEntry->publish( $logId );
826
827
		# Report to the user
828
		return true;
829
	}
830
831
	/**
832
	 * Get an array of suggested block durations from MediaWiki:Ipboptions
833
	 * @todo FIXME: This uses a rather odd syntax for the options, should it be converted
834
	 *     to the standard "**<duration>|<displayname>" format?
835
	 * @param Language|null $lang The language to get the durations in, or null to use
836
	 *     the wiki's content language
837
	 * @return array
838
	 */
839
	public static function getSuggestedDurations( $lang = null ) {
840
		$a = [];
841
		$msg = $lang === null
842
			? wfMessage( 'ipboptions' )->inContentLanguage()->text()
843
			: wfMessage( 'ipboptions' )->inLanguage( $lang )->text();
844
845
		if ( $msg == '-' ) {
846
			return [];
847
		}
848
849 View Code Duplication
		foreach ( explode( ',', $msg ) as $option ) {
850
			if ( strpos( $option, ':' ) === false ) {
851
				$option = "$option:$option";
852
			}
853
854
			list( $show, $value ) = explode( ':', $option );
855
			$a[$show] = $value;
856
		}
857
858
		return $a;
859
	}
860
861
	/**
862
	 * Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
863
	 * ("24 May 2034", etc), into an absolute timestamp we can put into the database.
864
	 * @param string $expiry Whatever was typed into the form
865
	 * @return string Timestamp or 'infinity'
866
	 */
867
	public static function parseExpiryInput( $expiry ) {
868 View Code Duplication
		if ( wfIsInfinity( $expiry ) ) {
869
			$expiry = 'infinity';
870
		} else {
871
			$expiry = strtotime( $expiry );
872
873
			if ( $expiry < 0 || $expiry === false ) {
874
				return false;
875
			}
876
877
			$expiry = wfTimestamp( TS_MW, $expiry );
878
		}
879
880
		return $expiry;
881
	}
882
883
	/**
884
	 * Can we do an email block?
885
	 * @param User $user The sysop wanting to make a block
886
	 * @return bool
887
	 */
888
	public static function canBlockEmail( $user ) {
889
		global $wgEnableUserEmail, $wgSysopEmailBans;
890
891
		return ( $wgEnableUserEmail && $wgSysopEmailBans && $user->isAllowed( 'blockemail' ) );
892
	}
893
894
	/**
895
	 * bug 15810: blocked admins should not be able to block/unblock
896
	 * others, and probably shouldn't be able to unblock themselves
897
	 * either.
898
	 * @param User|int|string $user
899
	 * @param User $performer User doing the request
900
	 * @return bool|string True or error message key
901
	 */
902
	public static function checkUnblockSelf( $user, User $performer ) {
903
		if ( is_int( $user ) ) {
904
			$user = User::newFromId( $user );
905
		} elseif ( is_string( $user ) ) {
906
			$user = User::newFromName( $user );
907
		}
908
909
		if ( $performer->isBlocked() ) {
910
			if ( $user instanceof User && $user->getId() == $performer->getId() ) {
911
				# User is trying to unblock themselves
912
				if ( $performer->isAllowed( 'unblockself' ) ) {
913
					return true;
914
					# User blocked themselves and is now trying to reverse it
915
				} elseif ( $performer->blockedBy() === $performer->getName() ) {
916
					return true;
917
				} else {
918
					return 'ipbnounblockself';
919
				}
920
			} else {
921
				# User is trying to block/unblock someone else
922
				return 'ipbblocked';
923
			}
924
		} else {
925
			return true;
926
		}
927
	}
928
929
	/**
930
	 * Return a comma-delimited list of "flags" to be passed to the log
931
	 * reader for this block, to provide more information in the logs
932
	 * @param array $data From HTMLForm data
933
	 * @param int $type Block::TYPE_ constant (USER, RANGE, or IP)
934
	 * @return string
935
	 */
936
	protected static function blockLogFlags( array $data, $type ) {
937
		global $wgBlockAllowsUTEdit;
938
		$flags = [];
939
940
		# when blocking a user the option 'anononly' is not available/has no effect
941
		# -> do not write this into log
942
		if ( !$data['HardBlock'] && $type != Block::TYPE_USER ) {
943
			// For grepping: message block-log-flags-anononly
944
			$flags[] = 'anononly';
945
		}
946
947
		if ( $data['CreateAccount'] ) {
948
			// For grepping: message block-log-flags-nocreate
949
			$flags[] = 'nocreate';
950
		}
951
952
		# Same as anononly, this is not displayed when blocking an IP address
953
		if ( !$data['AutoBlock'] && $type == Block::TYPE_USER ) {
954
			// For grepping: message block-log-flags-noautoblock
955
			$flags[] = 'noautoblock';
956
		}
957
958
		if ( $data['DisableEmail'] ) {
959
			// For grepping: message block-log-flags-noemail
960
			$flags[] = 'noemail';
961
		}
962
963
		if ( $wgBlockAllowsUTEdit && $data['DisableUTEdit'] ) {
964
			// For grepping: message block-log-flags-nousertalk
965
			$flags[] = 'nousertalk';
966
		}
967
968
		if ( $data['HideUser'] ) {
969
			// For grepping: message block-log-flags-hiddenname
970
			$flags[] = 'hiddenname';
971
		}
972
973
		return implode( ',', $flags );
974
	}
975
976
	/**
977
	 * Process the form on POST submission.
978
	 * @param array $data
979
	 * @param HTMLForm $form
980
	 * @return bool|array True for success, false for didn't-try, array of errors on failure
981
	 */
982
	public function onSubmit( array $data, HTMLForm $form = null ) {
983
		return self::processForm( $data, $form->getContext() );
0 ignored issues
show
Bug introduced by
It seems like $form is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
984
	}
985
986
	/**
987
	 * Do something exciting on successful processing of the form, most likely to show a
988
	 * confirmation message
989
	 */
990
	public function onSuccess() {
991
		$out = $this->getOutput();
992
		$out->setPageTitle( $this->msg( 'blockipsuccesssub' ) );
993
		$out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
0 ignored issues
show
Bug introduced by
It seems like $this->target can also be of type null or object<User>; however, wfEscapeWikiText() does only seem to accept string, 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...
994
	}
995
996
	/**
997
	 * Return an array of subpages beginning with $search that this special page will accept.
998
	 *
999
	 * @param string $search Prefix to search for
1000
	 * @param int $limit Maximum number of results to return (usually 10)
1001
	 * @param int $offset Number of results to skip (usually 0)
1002
	 * @return string[] Matching subpages
1003
	 */
1004 View Code Duplication
	public function prefixSearchSubpages( $search, $limit, $offset ) {
1005
		$user = User::newFromName( $search );
1006
		if ( !$user ) {
1007
			// No prefix suggestion for invalid user
1008
			return [];
1009
		}
1010
		// Autocomplete subpage as user list - public to allow caching
1011
		return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
1012
	}
1013
1014
	protected function getGroupName() {
1015
		return 'users';
1016
	}
1017
}
1018