Completed
Branch master (939199)
by
unknown
39:35
created

includes/specials/SpecialBlock.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 );
69
		if ( $status !== true ) {
70
			throw new ErrorPageError( 'badaccess', $status );
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() );
246
			if ( !$status->isOK() ) {
247
				$errors = $status->getErrorsArray();
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 );
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 );
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 )
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();
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
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
			$user = null;
649
			$userId = 0;
650
		} elseif ( $type == Block::TYPE_IP ) {
651
			$user = null;
652
			$target = $target->getName();
653
			$userId = 0;
654
		} else {
655
			# This should have been caught in the form field validation
656
			return [ 'badipaddress' ];
657
		}
658
659
		$expiryTime = self::parseExpiryInput( $data['Expiry'] );
660
661
		if (
662
			// an expiry time is needed
663
			( strlen( $data['Expiry'] ) == 0 ) ||
664
			// can't be a larger string as 50 (it should be a time format in any way)
665
			( strlen( $data['Expiry'] ) > 50 ) ||
666
			// check, if the time could be parsed
667
			!$expiryTime
668
		) {
669
			return [ 'ipb_expiry_invalid' ];
670
		}
671
672
		// an expiry time should be in the future, not in the
673
		// past (wouldn't make any sense) - bug T123069
674
		if ( $expiryTime < wfTimestampNow() ) {
675
			return [ 'ipb_expiry_old' ];
676
		}
677
678
		if ( !isset( $data['DisableEmail'] ) ) {
679
			$data['DisableEmail'] = false;
680
		}
681
682
		# If the user has done the form 'properly', they won't even have been given the
683
		# option to suppress-block unless they have the 'hideuser' permission
684
		if ( !isset( $data['HideUser'] ) ) {
685
			$data['HideUser'] = false;
686
		}
687
688
		if ( $data['HideUser'] ) {
689
			if ( !$performer->isAllowed( 'hideuser' ) ) {
690
				# this codepath is unreachable except by a malicious user spoofing forms,
691
				# or by race conditions (user has hideuser and block rights, loads block form,
692
				# and loses hideuser rights before submission); so need to fail completely
693
				# rather than just silently disable hiding
694
				return [ 'badaccess-group0' ];
695
			}
696
697
			# Recheck params here...
698
			if ( $type != Block::TYPE_USER ) {
699
				$data['HideUser'] = false; # IP users should not be hidden
700
			} elseif ( !wfIsInfinity( $data['Expiry'] ) ) {
701
				# Bad expiry.
702
				return [ 'ipb_expiry_temp' ];
703
			} elseif ( $wgHideUserContribLimit !== false
704
				&& $user->getEditCount() > $wgHideUserContribLimit
705
			) {
706
				# Typically, the user should have a handful of edits.
707
				# Disallow hiding users with many edits for performance.
708
				return [ [ 'ipb_hide_invalid',
709
					Message::numParam( $wgHideUserContribLimit ) ] ];
710
			} elseif ( !$data['Confirm'] ) {
711
				return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
712
			}
713
		}
714
715
		# Create block object.
716
		$block = new Block();
717
		$block->setTarget( $target );
718
		$block->setBlocker( $performer );
719
		# Truncate reason for whole multibyte characters
720
		$block->mReason = $wgContLang->truncate( $data['Reason'][0], 255 );
721
		$block->mExpiry = $expiryTime;
722
		$block->prevents( 'createaccount', $data['CreateAccount'] );
723
		$block->prevents( 'editownusertalk', ( !$wgBlockAllowsUTEdit || $data['DisableUTEdit'] ) );
724
		$block->prevents( 'sendemail', $data['DisableEmail'] );
725
		$block->isHardblock( $data['HardBlock'] );
726
		$block->isAutoblocking( $data['AutoBlock'] );
727
		$block->mHideName = $data['HideUser'];
728
729
		$reason = [ 'hookaborted' ];
730
		if ( !Hooks::run( 'BlockIp', [ &$block, &$performer, &$reason ] ) ) {
731
			return $reason;
732
		}
733
734
		$priorBlock = null;
735
		# Try to insert block. Is there a conflicting block?
736
		$status = $block->insert();
737
		if ( !$status ) {
738
			# Indicates whether the user is confirming the block and is aware of
739
			# the conflict (did not change the block target in the meantime)
740
			$blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
741
				&& $data['PreviousTarget'] !== $target );
742
743
			# Special case for API - bug 32434
744
			$reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
745
746
			# Show form unless the user is already aware of this...
747
			if ( $blockNotConfirmed || $reblockNotAllowed ) {
748
				return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
749
				# Otherwise, try to update the block...
750
			} else {
751
				# This returns direct blocks before autoblocks/rangeblocks, since we should
752
				# be sure the user is blocked by now it should work for our purposes
753
				$currentBlock = Block::newFromTarget( $target );
754
				if ( $block->equals( $currentBlock ) ) {
0 ignored issues
show
It seems like $currentBlock defined by \Block::newFromTarget($target) on line 753 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...
755
					return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
756
				}
757
				# If the name was hidden and the blocking user cannot hide
758
				# names, then don't allow any block changes...
759
				if ( $currentBlock->mHideName && !$performer->isAllowed( 'hideuser' ) ) {
760
					return [ 'cant-see-hidden-user' ];
761
				}
762
763
				$priorBlock = clone $currentBlock;
764
				$currentBlock->isHardblock( $block->isHardblock() );
765
				$currentBlock->prevents( 'createaccount', $block->prevents( 'createaccount' ) );
766
				$currentBlock->mExpiry = $block->mExpiry;
767
				$currentBlock->isAutoblocking( $block->isAutoblocking() );
768
				$currentBlock->mHideName = $block->mHideName;
769
				$currentBlock->prevents( 'sendemail', $block->prevents( 'sendemail' ) );
770
				$currentBlock->prevents( 'editownusertalk', $block->prevents( 'editownusertalk' ) );
771
				$currentBlock->mReason = $block->mReason;
772
773
				$status = $currentBlock->update();
774
775
				$logaction = 'reblock';
776
777
				# Unset _deleted fields if requested
778
				if ( $currentBlock->mHideName && !$data['HideUser'] ) {
779
					RevisionDeleteUser::unsuppressUserName( $target, $userId );
780
				}
781
782
				# If hiding/unhiding a name, this should go in the private logs
783
				if ( (bool)$currentBlock->mHideName ) {
784
					$data['HideUser'] = true;
785
				}
786
			}
787
		} else {
788
			$logaction = 'block';
789
		}
790
791
		Hooks::run( 'BlockIpComplete', [ $block, $performer, $priorBlock ] );
792
793
		# Set *_deleted fields if requested
794
		if ( $data['HideUser'] ) {
795
			RevisionDeleteUser::suppressUserName( $target, $userId );
796
		}
797
798
		# Can't watch a rangeblock
799
		if ( $type != Block::TYPE_RANGE && $data['Watch'] ) {
800
			WatchAction::doWatch(
801
				Title::makeTitle( NS_USER, $target ),
802
				$performer,
803
				User::IGNORE_USER_RIGHTS
804
			);
805
		}
806
807
		# Block constructor sanitizes certain block options on insert
808
		$data['BlockEmail'] = $block->prevents( 'sendemail' );
809
		$data['AutoBlock'] = $block->isAutoblocking();
810
811
		# Prepare log parameters
812
		$logParams = [];
813
		$logParams['5::duration'] = $data['Expiry'];
814
		$logParams['6::flags'] = self::blockLogFlags( $data, $type );
815
816
		# Make log entry, if the name is hidden, put it in the suppression log
817
		$log_type = $data['HideUser'] ? 'suppress' : 'block';
818
		$logEntry = new ManualLogEntry( $log_type, $logaction );
819
		$logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
820
		$logEntry->setComment( $data['Reason'][0] );
821
		$logEntry->setPerformer( $performer );
822
		$logEntry->setParameters( $logParams );
823
		# Relate log ID to block IDs (bug 25763)
824
		$blockIds = array_merge( [ $status['id'] ], $status['autoIds'] );
825
		$logEntry->setRelations( [ 'ipb_id' => $blockIds ] );
826
		$logId = $logEntry->insert();
827
		$logEntry->publish( $logId );
828
829
		# Report to the user
830
		return true;
831
	}
832
833
	/**
834
	 * Get an array of suggested block durations from MediaWiki:Ipboptions
835
	 * @todo FIXME: This uses a rather odd syntax for the options, should it be converted
836
	 *     to the standard "**<duration>|<displayname>" format?
837
	 * @param Language|null $lang The language to get the durations in, or null to use
838
	 *     the wiki's content language
839
	 * @return array
840
	 */
841
	public static function getSuggestedDurations( $lang = null ) {
842
		$a = [];
843
		$msg = $lang === null
844
			? wfMessage( 'ipboptions' )->inContentLanguage()->text()
845
			: wfMessage( 'ipboptions' )->inLanguage( $lang )->text();
846
847
		if ( $msg == '-' ) {
848
			return [];
849
		}
850
851 View Code Duplication
		foreach ( explode( ',', $msg ) as $option ) {
852
			if ( strpos( $option, ':' ) === false ) {
853
				$option = "$option:$option";
854
			}
855
856
			list( $show, $value ) = explode( ':', $option );
857
			$a[$show] = $value;
858
		}
859
860
		return $a;
861
	}
862
863
	/**
864
	 * Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
865
	 * ("24 May 2034", etc), into an absolute timestamp we can put into the database.
866
	 * @param string $expiry Whatever was typed into the form
867
	 * @return string Timestamp or 'infinity'
868
	 */
869
	public static function parseExpiryInput( $expiry ) {
870 View Code Duplication
		if ( wfIsInfinity( $expiry ) ) {
871
			$expiry = 'infinity';
872
		} else {
873
			$expiry = strtotime( $expiry );
874
875
			if ( $expiry < 0 || $expiry === false ) {
876
				return false;
877
			}
878
879
			$expiry = wfTimestamp( TS_MW, $expiry );
880
		}
881
882
		return $expiry;
883
	}
884
885
	/**
886
	 * Can we do an email block?
887
	 * @param User $user The sysop wanting to make a block
888
	 * @return bool
889
	 */
890
	public static function canBlockEmail( $user ) {
891
		global $wgEnableUserEmail, $wgSysopEmailBans;
892
893
		return ( $wgEnableUserEmail && $wgSysopEmailBans && $user->isAllowed( 'blockemail' ) );
894
	}
895
896
	/**
897
	 * bug 15810: blocked admins should not be able to block/unblock
898
	 * others, and probably shouldn't be able to unblock themselves
899
	 * either.
900
	 * @param User|int|string $user
901
	 * @param User $performer User doing the request
902
	 * @return bool|string True or error message key
903
	 */
904
	public static function checkUnblockSelf( $user, User $performer ) {
905
		if ( is_int( $user ) ) {
906
			$user = User::newFromId( $user );
907
		} elseif ( is_string( $user ) ) {
908
			$user = User::newFromName( $user );
909
		}
910
911
		if ( $performer->isBlocked() ) {
912
			if ( $user instanceof User && $user->getId() == $performer->getId() ) {
913
				# User is trying to unblock themselves
914
				if ( $performer->isAllowed( 'unblockself' ) ) {
915
					return true;
916
					# User blocked themselves and is now trying to reverse it
917
				} elseif ( $performer->blockedBy() === $performer->getName() ) {
918
					return true;
919
				} else {
920
					return 'ipbnounblockself';
921
				}
922
			} else {
923
				# User is trying to block/unblock someone else
924
				return 'ipbblocked';
925
			}
926
		} else {
927
			return true;
928
		}
929
	}
930
931
	/**
932
	 * Return a comma-delimited list of "flags" to be passed to the log
933
	 * reader for this block, to provide more information in the logs
934
	 * @param array $data From HTMLForm data
935
	 * @param int $type Block::TYPE_ constant (USER, RANGE, or IP)
936
	 * @return string
937
	 */
938
	protected static function blockLogFlags( array $data, $type ) {
939
		global $wgBlockAllowsUTEdit;
940
		$flags = [];
941
942
		# when blocking a user the option 'anononly' is not available/has no effect
943
		# -> do not write this into log
944
		if ( !$data['HardBlock'] && $type != Block::TYPE_USER ) {
945
			// For grepping: message block-log-flags-anononly
946
			$flags[] = 'anononly';
947
		}
948
949
		if ( $data['CreateAccount'] ) {
950
			// For grepping: message block-log-flags-nocreate
951
			$flags[] = 'nocreate';
952
		}
953
954
		# Same as anononly, this is not displayed when blocking an IP address
955
		if ( !$data['AutoBlock'] && $type == Block::TYPE_USER ) {
956
			// For grepping: message block-log-flags-noautoblock
957
			$flags[] = 'noautoblock';
958
		}
959
960
		if ( $data['DisableEmail'] ) {
961
			// For grepping: message block-log-flags-noemail
962
			$flags[] = 'noemail';
963
		}
964
965
		if ( $wgBlockAllowsUTEdit && $data['DisableUTEdit'] ) {
966
			// For grepping: message block-log-flags-nousertalk
967
			$flags[] = 'nousertalk';
968
		}
969
970
		if ( $data['HideUser'] ) {
971
			// For grepping: message block-log-flags-hiddenname
972
			$flags[] = 'hiddenname';
973
		}
974
975
		return implode( ',', $flags );
976
	}
977
978
	/**
979
	 * Process the form on POST submission.
980
	 * @param array $data
981
	 * @param HTMLForm $form
982
	 * @return bool|array True for success, false for didn't-try, array of errors on failure
983
	 */
984
	public function onSubmit( array $data, HTMLForm $form = null ) {
985
		return self::processForm( $data, $form->getContext() );
986
	}
987
988
	/**
989
	 * Do something exciting on successful processing of the form, most likely to show a
990
	 * confirmation message
991
	 */
992
	public function onSuccess() {
993
		$out = $this->getOutput();
994
		$out->setPageTitle( $this->msg( 'blockipsuccesssub' ) );
995
		$out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
996
	}
997
998
	/**
999
	 * Return an array of subpages beginning with $search that this special page will accept.
1000
	 *
1001
	 * @param string $search Prefix to search for
1002
	 * @param int $limit Maximum number of results to return (usually 10)
1003
	 * @param int $offset Number of results to skip (usually 0)
1004
	 * @return string[] Matching subpages
1005
	 */
1006 View Code Duplication
	public function prefixSearchSubpages( $search, $limit, $offset ) {
1007
		$user = User::newFromName( $search );
1008
		if ( !$user ) {
1009
			// No prefix suggestion for invalid user
1010
			return [];
1011
		}
1012
		// Autocomplete subpage as user list - public to allow caching
1013
		return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
1014
	}
1015
1016
	protected function getGroupName() {
1017
		return 'users';
1018
	}
1019
}
1020