MediaWiki REL1_34
SpecialBlock.php
Go to the documentation of this file.
1<?php
29
40 protected $target;
41
43 protected $type;
44
46 protected $previousTarget;
47
50
52 protected $alreadyBlocked;
53
55 protected $preErrors = [];
56
57 public function __construct() {
58 parent::__construct( 'Block', 'block' );
59 }
60
61 public function doesWrites() {
62 return true;
63 }
64
71 protected function checkExecutePermissions( User $user ) {
72 parent::checkExecutePermissions( $user );
73 # T17810: blocked admins should have limited access here
74 $status = self::checkUnblockSelf( $this->target, $user );
75 if ( $status !== true ) {
76 throw new ErrorPageError( 'badaccess', $status );
77 }
78 }
79
85 public function requiresUnblock() {
86 return false;
87 }
88
94 protected function setParameter( $par ) {
95 # Extract variables from the request. Try not to get into a situation where we
96 # need to extract *every* variable from the form just for processing here, but
97 # there are legitimate uses for some variables
98 $request = $this->getRequest();
99 list( $this->target, $this->type ) = self::getTargetAndType( $par, $request );
100 if ( $this->target instanceof User ) {
101 # Set the 'relevant user' in the skin, so it displays links like Contributions,
102 # User logs, UserRights, etc.
103 $this->getSkin()->setRelevantUser( $this->target );
104 }
105
106 list( $this->previousTarget, /*...*/ ) =
107 DatabaseBlock::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
108 $this->requestedHideUser = $request->getBool( 'wpHideUser' );
109 }
110
116 protected function alterForm( HTMLForm $form ) {
117 $form->setHeaderText( '' );
118 $form->setSubmitDestructive();
119
120 $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
121 $form->setSubmitTextMsg( $msg );
122
123 $this->addHelpLink( 'Help:Blocking users' );
124
125 # Don't need to do anything if the form has been posted
126 if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
127 $s = $form->formatErrors( $this->preErrors );
128 if ( $s ) {
129 $form->addHeaderText( Html::rawElement(
130 'div',
131 [ 'class' => 'error' ],
132 $s
133 ) );
134 }
135 }
136 }
137
138 protected function getDisplayFormat() {
139 return 'ooui';
140 }
141
146 protected function getFormFields() {
147 $conf = $this->getConfig();
148 $enablePartialBlocks = $conf->get( 'EnablePartialBlocks' );
149 $blockAllowsUTEdit = $conf->get( 'BlockAllowsUTEdit' );
150
151 $this->getOutput()->enableOOUI();
152
153 $user = $this->getUser();
154
155 $suggestedDurations = self::getSuggestedDurations();
156
157 $a = [];
158
159 $a['Target'] = [
160 'type' => 'user',
161 'ipallowed' => true,
162 'iprange' => true,
163 'id' => 'mw-bi-target',
164 'size' => '45',
165 'autofocus' => true,
166 'required' => true,
167 'validation-callback' => [ __CLASS__, 'validateTargetField' ],
168 'section' => 'target',
169 ];
170
171 $a['Editing'] = [
172 'type' => 'check',
173 'label-message' => 'block-prevent-edit',
174 'default' => true,
175 'section' => 'actions',
176 'disabled' => $enablePartialBlocks ? false : true,
177 ];
178
179 if ( $enablePartialBlocks ) {
180 $a['EditingRestriction'] = [
181 'type' => 'radio',
182 'cssclass' => 'mw-block-editing-restriction',
183 'options' => [
184 $this->msg( 'ipb-sitewide' )->escaped() .
185 new \OOUI\LabelWidget( [
186 'classes' => [ 'oo-ui-inline-help' ],
187 'label' => $this->msg( 'ipb-sitewide-help' )->text(),
188 ] ) => 'sitewide',
189 $this->msg( 'ipb-partial' )->escaped() .
190 new \OOUI\LabelWidget( [
191 'classes' => [ 'oo-ui-inline-help' ],
192 'label' => $this->msg( 'ipb-partial-help' )->text(),
193 ] ) => 'partial',
194 ],
195 'section' => 'actions',
196 ];
197 $a['PageRestrictions'] = [
198 'type' => 'titlesmultiselect',
199 'label' => $this->msg( 'ipb-pages-label' )->text(),
200 'exists' => true,
201 'max' => 10,
202 'cssclass' => 'mw-block-restriction',
203 'showMissing' => false,
204 'excludeDynamicNamespaces' => true,
205 'input' => [
206 'autocomplete' => false
207 ],
208 'section' => 'actions',
209 ];
210 $a['NamespaceRestrictions'] = [
211 'type' => 'namespacesmultiselect',
212 'label' => $this->msg( 'ipb-namespaces-label' )->text(),
213 'exists' => true,
214 'cssclass' => 'mw-block-restriction',
215 'input' => [
216 'autocomplete' => false
217 ],
218 'section' => 'actions',
219 ];
220 }
221
222 $a['CreateAccount'] = [
223 'type' => 'check',
224 'label-message' => 'ipbcreateaccount',
225 'default' => true,
226 'section' => 'actions',
227 ];
228
229 if ( self::canBlockEmail( $user ) ) {
230 $a['DisableEmail'] = [
231 'type' => 'check',
232 'label-message' => 'ipbemailban',
233 'section' => 'actions',
234 ];
235 }
236
237 if ( $blockAllowsUTEdit ) {
238 $a['DisableUTEdit'] = [
239 'type' => 'check',
240 'label-message' => 'ipb-disableusertalk',
241 'default' => false,
242 'section' => 'actions',
243 ];
244 }
245
246 $a['Expiry'] = [
247 'type' => 'expiry',
248 'required' => true,
249 'options' => $suggestedDurations,
250 'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
251 'section' => 'expiry',
252 ];
253
254 $a['Reason'] = [
255 'type' => 'selectandother',
256 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
257 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
258 // Unicode codepoints.
259 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
260 'maxlength-unit' => 'codepoints',
261 'options-message' => 'ipbreason-dropdown',
262 'section' => 'reason',
263 ];
264
265 $a['AutoBlock'] = [
266 'type' => 'check',
267 'label-message' => 'ipbenableautoblock',
268 'default' => true,
269 'section' => 'options',
270 ];
271
272 # Allow some users to hide name from block log, blocklist and listusers
273 if ( MediaWikiServices::getInstance()
275 ->userHasRight( $user, 'hideuser' )
276 ) {
277 $a['HideUser'] = [
278 'type' => 'check',
279 'label-message' => 'ipbhidename',
280 'cssclass' => 'mw-block-hideuser',
281 'section' => 'options',
282 ];
283 }
284
285 # Watchlist their user page? (Only if user is logged in)
286 if ( $user->isLoggedIn() ) {
287 $a['Watch'] = [
288 'type' => 'check',
289 'label-message' => 'ipbwatchuser',
290 'section' => 'options',
291 ];
292 }
293
294 $a['HardBlock'] = [
295 'type' => 'check',
296 'label-message' => 'ipb-hardblock',
297 'default' => false,
298 'section' => 'options',
299 ];
300
301 # This is basically a copy of the Target field, but the user can't change it, so we
302 # can see if the warnings we maybe showed to the user before still apply
303 $a['PreviousTarget'] = [
304 'type' => 'hidden',
305 'default' => false,
306 ];
307
308 # We'll turn this into a checkbox if we need to
309 $a['Confirm'] = [
310 'type' => 'hidden',
311 'default' => '',
312 'label-message' => 'ipb-confirm',
313 'cssclass' => 'mw-block-confirm',
314 ];
315
316 $this->maybeAlterFormDefaults( $a );
317
318 // Allow extensions to add more fields
319 Hooks::run( 'SpecialBlockModifyFormFields', [ $this, &$a ] );
320
321 return $a;
322 }
323
329 protected function maybeAlterFormDefaults( &$fields ) {
330 # This will be overwritten by request data
331 $fields['Target']['default'] = (string)$this->target;
332
333 if ( $this->target ) {
334 $status = self::validateTarget( $this->target, $this->getUser() );
335 if ( !$status->isOK() ) {
336 $errors = $status->getErrorsArray();
337 $this->preErrors = array_merge( $this->preErrors, $errors );
338 }
339 }
340
341 # This won't be
342 $fields['PreviousTarget']['default'] = (string)$this->target;
343
344 $block = DatabaseBlock::newFromTarget( $this->target );
345
346 // Populate fields if there is a block that is not an autoblock; if it is a range
347 // block, only populate the fields if the range is the same as $this->target
348 if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
349 && ( $this->type != DatabaseBlock::TYPE_RANGE
350 || $block->getTarget() == $this->target )
351 ) {
352 $fields['HardBlock']['default'] = $block->isHardblock();
353 $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked();
354 $fields['AutoBlock']['default'] = $block->isAutoblocking();
355
356 if ( isset( $fields['DisableEmail'] ) ) {
357 $fields['DisableEmail']['default'] = $block->isEmailBlocked();
358 }
359
360 if ( isset( $fields['HideUser'] ) ) {
361 $fields['HideUser']['default'] = $block->getHideName();
362 }
363
364 if ( isset( $fields['DisableUTEdit'] ) ) {
365 $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed();
366 }
367
368 // If the username was hidden (ipb_deleted == 1), don't show the reason
369 // unless this user also has rights to hideuser: T37839
370 if ( !$block->getHideName() || MediaWikiServices::getInstance()
371 ->getPermissionManager()
372 ->userHasRight( $this->getUser(), 'hideuser' )
373 ) {
374 $fields['Reason']['default'] = $block->getReason();
375 } else {
376 $fields['Reason']['default'] = '';
377 }
378
379 if ( $this->getRequest()->wasPosted() ) {
380 # Ok, so we got a POST submission asking us to reblock a user. So show the
381 # confirm checkbox; the user will only see it if they haven't previously
382 $fields['Confirm']['type'] = 'check';
383 } else {
384 # We got a target, but it wasn't a POST request, so the user must have gone
385 # to a link like [[Special:Block/User]]. We don't need to show the checkbox
386 # as long as they go ahead and block *that* user
387 $fields['Confirm']['default'] = 1;
388 }
389
390 if ( $block->getExpiry() == 'infinity' ) {
391 $fields['Expiry']['default'] = 'infinite';
392 } else {
393 $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() );
394 }
395
396 $this->alreadyBlocked = true;
397 $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( (string)$block->getTarget() ) ];
398 }
399
400 if ( $this->alreadyBlocked || $this->getRequest()->wasPosted()
401 || $this->getRequest()->getCheck( 'wpCreateAccount' )
402 ) {
403 $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true );
404 }
405
406 # We always need confirmation to do HideUser
407 if ( $this->requestedHideUser ) {
408 $fields['Confirm']['type'] = 'check';
409 unset( $fields['Confirm']['default'] );
410 $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
411 }
412
413 # Or if the user is trying to block themselves
414 if ( (string)$this->target === $this->getUser()->getName() ) {
415 $fields['Confirm']['type'] = 'check';
416 unset( $fields['Confirm']['default'] );
417 $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
418 }
419
420 if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) {
421 if ( $block instanceof DatabaseBlock && !$block->isSitewide() ) {
422 $fields['EditingRestriction']['default'] = 'partial';
423 } else {
424 $fields['EditingRestriction']['default'] = 'sitewide';
425 }
426
427 if ( $block instanceof DatabaseBlock ) {
428 $pageRestrictions = [];
429 $namespaceRestrictions = [];
430 foreach ( $block->getRestrictions() as $restriction ) {
431 switch ( $restriction->getType() ) {
432 case PageRestriction::TYPE:
434 '@phan-var PageRestriction $restriction';
435 if ( $restriction->getTitle() ) {
436 $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
437 }
438 break;
439 case NamespaceRestriction::TYPE:
440 $namespaceRestrictions[] = $restriction->getValue();
441 break;
442 }
443 }
444
445 if (
446 !$block->isSitewide() &&
447 empty( $pageRestrictions ) &&
448 empty( $namespaceRestrictions )
449 ) {
450 $fields['Editing']['default'] = false;
451 }
452
453 // Sort the restrictions so they are in alphabetical order.
454 sort( $pageRestrictions );
455 $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
456 sort( $namespaceRestrictions );
457 $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions );
458 }
459 }
460 }
461
466 protected function preText() {
467 $this->getOutput()->addModuleStyles( [
468 'mediawiki.widgets.TagMultiselectWidget.styles',
469 'mediawiki.special',
470 ] );
471 $this->getOutput()->addModules( [ 'mediawiki.special.block' ] );
472
473 $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
474 $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
475
476 $otherBlockMessages = [];
477 if ( $this->target !== null ) {
478 $targetName = $this->target;
479 if ( $this->target instanceof User ) {
480 $targetName = $this->target->getName();
481 }
482 # Get other blocks, i.e. from GlobalBlocking or TorBlock extension
483 Hooks::run( 'OtherBlockLogLink', [ &$otherBlockMessages, $targetName ] );
484
485 if ( count( $otherBlockMessages ) ) {
486 $s = Html::rawElement(
487 'h2',
488 [],
489 $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
490 ) . "\n";
491
492 $list = '';
493
494 foreach ( $otherBlockMessages as $link ) {
495 $list .= Html::rawElement( 'li', [], $link ) . "\n";
496 }
497
498 $s .= Html::rawElement(
499 'ul',
500 [ 'class' => 'mw-blockip-alreadyblocked' ],
501 $list
502 ) . "\n";
503
504 $text .= $s;
505 }
506 }
507
508 return $text;
509 }
510
515 protected function postText() {
516 $links = [];
517
518 $this->getOutput()->addModuleStyles( 'mediawiki.special' );
519
521 # Link to the user's contributions, if applicable
522 if ( $this->target instanceof User ) {
523 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
524 $links[] = $linkRenderer->makeLink(
525 $contribsPage,
526 $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
527 );
528 }
529
530 # Link to unblock the specified user, or to a blank unblock form
531 if ( $this->target instanceof User ) {
532 $message = $this->msg(
533 'ipb-unblock-addr',
534 wfEscapeWikiText( $this->target->getName() )
535 )->parse();
536 $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
537 } else {
538 $message = $this->msg( 'ipb-unblock' )->parse();
539 $list = SpecialPage::getTitleFor( 'Unblock' );
540 }
541 $links[] = $linkRenderer->makeKnownLink(
542 $list,
543 new HtmlArmor( $message )
544 );
545
546 # Link to the block list
547 $links[] = $linkRenderer->makeKnownLink(
548 SpecialPage::getTitleFor( 'BlockList' ),
549 $this->msg( 'ipb-blocklist' )->text()
550 );
551
552 $user = $this->getUser();
553
554 # Link to edit the block dropdown reasons, if applicable
555 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
556 if ( $permissionManager->userHasRight( $user, 'editinterface' ) ) {
557 $links[] = $linkRenderer->makeKnownLink(
558 $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
559 $this->msg( 'ipb-edit-dropdown' )->text(),
560 [],
561 [ 'action' => 'edit' ]
562 );
563 }
564
565 $text = Html::rawElement(
566 'p',
567 [ 'class' => 'mw-ipb-conveniencelinks' ],
568 $this->getLanguage()->pipeList( $links )
569 );
570
571 $userTitle = self::getTargetUserTitle( $this->target );
572 if ( $userTitle ) {
573 # Get relevant extracts from the block and suppression logs, if possible
574 $out = '';
575
577 $out,
578 'block',
579 $userTitle,
580 '',
581 [
582 'lim' => 10,
583 'msgKey' => [ 'blocklog-showlog', $userTitle->getText() ],
584 'showIfEmpty' => false
585 ]
586 );
587 $text .= $out;
588
589 # Add suppression block entries if allowed
590 if ( $permissionManager->userHasRight( $user, 'suppressionlog' ) ) {
592 $out,
593 'suppress',
594 $userTitle,
595 '',
596 [
597 'lim' => 10,
598 'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
599 'msgKey' => [ 'blocklog-showsuppresslog', $userTitle->getText() ],
600 'showIfEmpty' => false
601 ]
602 );
603
604 $text .= $out;
605 }
606 }
607
608 return $text;
609 }
610
617 protected static function getTargetUserTitle( $target ) {
618 if ( $target instanceof User ) {
619 return $target->getUserPage();
620 } elseif ( IP::isIPAddress( $target ) ) {
621 return Title::makeTitleSafe( NS_USER, $target );
622 }
623
624 return null;
625 }
626
636 public static function getTargetAndType( $par, WebRequest $request = null ) {
637 $i = 0;
638 $target = null;
639
640 while ( true ) {
641 switch ( $i++ ) {
642 case 0:
643 # The HTMLForm will check wpTarget first and only if it doesn't get
644 # a value use the default, which will be generated from the options
645 # below; so this has to have a higher precedence here than $par, or
646 # we could end up with different values in $this->target and the HTMLForm!
647 if ( $request instanceof WebRequest ) {
648 $target = $request->getText( 'wpTarget', null );
649 }
650 break;
651 case 1:
652 $target = $par;
653 break;
654 case 2:
655 if ( $request instanceof WebRequest ) {
656 $target = $request->getText( 'ip', null );
657 }
658 break;
659 case 3:
660 # B/C @since 1.18
661 if ( $request instanceof WebRequest ) {
662 $target = $request->getText( 'wpBlockAddress', null );
663 }
664 break;
665 case 4:
666 break 2;
667 }
668
669 list( $target, $type ) = DatabaseBlock::parseTarget( $target );
670
671 if ( $type !== null ) {
672 return [ $target, $type ];
673 }
674 }
675
676 return [ null, null ];
677 }
678
687 public static function validateTargetField( $value, $alldata, $form ) {
688 $status = self::validateTarget( $value, $form->getUser() );
689 if ( !$status->isOK() ) {
690 $errors = $status->getErrorsArray();
691
692 return $form->msg( ...$errors[0] );
693 } else {
694 return true;
695 }
696 }
697
706 public static function validateTarget( $value, User $user ) {
707 global $wgBlockCIDRLimit;
708
710 list( $target, $type ) = self::getTargetAndType( $value );
711 $status = Status::newGood( $target );
712
713 if ( $type == DatabaseBlock::TYPE_USER ) {
714 if ( $target->isAnon() ) {
715 $status->fatal(
716 'nosuchusershort',
718 );
719 }
720
721 $unblockStatus = self::checkUnblockSelf( $target, $user );
722 if ( $unblockStatus !== true ) {
723 $status->fatal( 'badaccess', $unblockStatus );
724 }
725 } elseif ( $type == DatabaseBlock::TYPE_RANGE ) {
726 list( $ip, $range ) = explode( '/', $target, 2 );
727
728 if (
729 ( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 ) ||
730 ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 )
731 ) {
732 // Range block effectively disabled
733 $status->fatal( 'range_block_disabled' );
734 }
735
736 if (
737 ( IP::isIPv4( $ip ) && $range > 32 ) ||
738 ( IP::isIPv6( $ip ) && $range > 128 )
739 ) {
740 // Dodgy range
741 $status->fatal( 'ip_range_invalid' );
742 }
743
744 if ( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) {
745 $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
746 }
747
748 if ( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) {
749 $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
750 }
751 } elseif ( $type == DatabaseBlock::TYPE_IP ) {
752 # All is well
753 } else {
754 $status->fatal( 'badipaddress' );
755 }
756
757 return $status;
758 }
759
767 public static function processForm( array $data, IContextSource $context ) {
768 $performer = $context->getUser();
769 $enablePartialBlocks = $context->getConfig()->get( 'EnablePartialBlocks' );
770 $isPartialBlock = $enablePartialBlocks &&
771 isset( $data['EditingRestriction'] ) &&
772 $data['EditingRestriction'] === 'partial';
773
774 # This might have been a hidden field or a checkbox, so interesting data
775 # can come from it
776 $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
777
779 list( $target, $type ) = self::getTargetAndType( $data['Target'] );
780 if ( $type == DatabaseBlock::TYPE_USER ) {
781 $user = $target;
782 $target = $user->getName();
783 $userId = $user->getId();
784
785 # Give admins a heads-up before they go and block themselves. Much messier
786 # to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
787 # permission anyway, although the code does allow for it.
788 # Note: Important to use $target instead of $data['Target']
789 # since both $data['PreviousTarget'] and $target are normalized
790 # but $data['target'] gets overridden by (non-normalized) request variable
791 # from previous request.
792 if ( $target === $performer->getName() &&
793 ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
794 ) {
795 return [ 'ipb-blockingself', 'ipb-confirmaction' ];
796 }
797 } elseif ( $type == DatabaseBlock::TYPE_RANGE ) {
798 $user = null;
799 $userId = 0;
800 } elseif ( $type == DatabaseBlock::TYPE_IP ) {
801 $user = null;
803 $userId = 0;
804 } else {
805 # This should have been caught in the form field validation
806 return [ 'badipaddress' ];
807 }
808
809 $expiryTime = self::parseExpiryInput( $data['Expiry'] );
810
811 if (
812 // an expiry time is needed
813 ( strlen( $data['Expiry'] ) == 0 ) ||
814 // can't be a larger string as 50 (it should be a time format in any way)
815 ( strlen( $data['Expiry'] ) > 50 ) ||
816 // check, if the time could be parsed
818 ) {
819 return [ 'ipb_expiry_invalid' ];
820 }
821
822 // an expiry time should be in the future, not in the
823 // past (wouldn't make any sense) - bug T123069
824 if ( $expiryTime < wfTimestampNow() ) {
825 return [ 'ipb_expiry_old' ];
826 }
827
828 if ( !isset( $data['DisableEmail'] ) ) {
829 $data['DisableEmail'] = false;
830 }
831
832 # If the user has done the form 'properly', they won't even have been given the
833 # option to suppress-block unless they have the 'hideuser' permission
834 if ( !isset( $data['HideUser'] ) ) {
835 $data['HideUser'] = false;
836 }
837
838 if ( $data['HideUser'] ) {
839 if ( !MediaWikiServices::getInstance()
841 ->userHasRight( $performer, 'hideuser' )
842 ) {
843 # this codepath is unreachable except by a malicious user spoofing forms,
844 # or by race conditions (user has hideuser and block rights, loads block form,
845 # and loses hideuser rights before submission); so need to fail completely
846 # rather than just silently disable hiding
847 return [ 'badaccess-group0' ];
848 }
849
850 if ( $isPartialBlock ) {
851 return [ 'ipb_hide_partial' ];
852 }
853
854 # Recheck params here...
855 $hideUserContribLimit = $context->getConfig()->get( 'HideUserContribLimit' );
856 if ( $type != DatabaseBlock::TYPE_USER ) {
857 $data['HideUser'] = false; # IP users should not be hidden
858 } elseif ( !wfIsInfinity( $data['Expiry'] ) ) {
859 # Bad expiry.
860 return [ 'ipb_expiry_temp' ];
861 } elseif ( $hideUserContribLimit !== false
862 && $user->getEditCount() > $hideUserContribLimit
863 ) {
864 # Typically, the user should have a handful of edits.
865 # Disallow hiding users with many edits for performance.
866 return [ [ 'ipb_hide_invalid',
867 Message::numParam( $hideUserContribLimit ) ] ];
868 } elseif ( !$data['Confirm'] ) {
869 return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
870 }
871 }
872
873 $blockAllowsUTEdit = $context->getConfig()->get( 'BlockAllowsUTEdit' );
874 $userTalkEditAllowed = !$blockAllowsUTEdit || !$data['DisableUTEdit'];
875 if ( !$userTalkEditAllowed &&
876 $isPartialBlock &&
877 !in_array( NS_USER_TALK, explode( "\n", $data['NamespaceRestrictions'] ) )
878 ) {
879 return [ 'ipb-prevent-user-talk-edit' ];
880 }
881
882 # Create block object.
883 $block = new DatabaseBlock();
884 $block->setTarget( $target );
885 $block->setBlocker( $performer );
886 $block->setReason( $data['Reason'][0] );
887 $block->setExpiry( $expiryTime );
888 $block->isCreateAccountBlocked( $data['CreateAccount'] );
889 $block->isUsertalkEditAllowed( $userTalkEditAllowed );
890 $block->isEmailBlocked( $data['DisableEmail'] );
891 $block->isHardblock( $data['HardBlock'] );
892 $block->isAutoblocking( $data['AutoBlock'] );
893 $block->setHideName( $data['HideUser'] );
894
895 if ( $isPartialBlock ) {
896 $block->isSitewide( false );
897 }
898
899 $reason = [ 'hookaborted' ];
900 if ( !Hooks::run( 'BlockIp', [ &$block, &$performer, &$reason ] ) ) {
901 return $reason;
902 }
903
904 $pageRestrictions = [];
905 $namespaceRestrictions = [];
906 if ( $enablePartialBlocks ) {
907 if ( $data['PageRestrictions'] !== '' ) {
908 $pageRestrictions = array_map( function ( $text ) {
909 $title = Title::newFromText( $text );
910 // Use the link cache since the title has already been loaded when
911 // the field was validated.
912 $restriction = new PageRestriction( 0, $title->getArticleID() );
913 $restriction->setTitle( $title );
914 return $restriction;
915 }, explode( "\n", $data['PageRestrictions'] ) );
916 }
917 if ( $data['NamespaceRestrictions'] !== '' ) {
918 $namespaceRestrictions = array_map( function ( $id ) {
919 return new NamespaceRestriction( 0, $id );
920 }, explode( "\n", $data['NamespaceRestrictions'] ) );
921 }
922
923 $restrictions = ( array_merge( $pageRestrictions, $namespaceRestrictions ) );
924 $block->setRestrictions( $restrictions );
925 }
926
927 $priorBlock = null;
928 # Try to insert block. Is there a conflicting block?
929 $status = $block->insert();
930 if ( !$status ) {
931 # Indicates whether the user is confirming the block and is aware of
932 # the conflict (did not change the block target in the meantime)
933 $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
934 && $data['PreviousTarget'] !== $target );
935
936 # Special case for API - T34434
937 $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
938
939 # Show form unless the user is already aware of this...
940 if ( $blockNotConfirmed || $reblockNotAllowed ) {
941 return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
942 # Otherwise, try to update the block...
943 } else {
944 # This returns direct blocks before autoblocks/rangeblocks, since we should
945 # be sure the user is blocked by now it should work for our purposes
946 $currentBlock = DatabaseBlock::newFromTarget( $target );
947 if ( $block->equals( $currentBlock ) ) {
948 return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
949 }
950 # If the name was hidden and the blocking user cannot hide
951 # names, then don't allow any block changes...
952 if ( $currentBlock->getHideName() && !MediaWikiServices::getInstance()
953 ->getPermissionManager()
954 ->userHasRight( $performer, 'hideuser' )
955 ) {
956 return [ 'cant-see-hidden-user' ];
957 }
958
959 $priorBlock = clone $currentBlock;
960 $currentBlock->isHardblock( $block->isHardblock() );
961 $currentBlock->isCreateAccountBlocked( $block->isCreateAccountBlocked() );
962 $currentBlock->setExpiry( $block->getExpiry() );
963 $currentBlock->isAutoblocking( $block->isAutoblocking() );
964 $currentBlock->setHideName( $block->getHideName() );
965 $currentBlock->isEmailBlocked( $block->isEmailBlocked() );
966 $currentBlock->isUsertalkEditAllowed( $block->isUsertalkEditAllowed() );
967 $currentBlock->setReason( $block->getReason() );
968
969 if ( $enablePartialBlocks ) {
970 // Maintain the sitewide status. If partial blocks is not enabled,
971 // saving the block will result in a sitewide block.
972 $currentBlock->isSitewide( $block->isSitewide() );
973
974 // Set the block id of the restrictions.
975 $blockRestrictionStore = MediaWikiServices::getInstance()->getBlockRestrictionStore();
976 $currentBlock->setRestrictions(
977 $blockRestrictionStore->setBlockId( $currentBlock->getId(), $restrictions )
978 );
979 }
980
981 $status = $currentBlock->update();
982 // TODO handle failure
983
984 $logaction = 'reblock';
985
986 # Unset _deleted fields if requested
987 if ( $currentBlock->getHideName() && !$data['HideUser'] ) {
989 }
990
991 # If hiding/unhiding a name, this should go in the private logs
992 if ( (bool)$currentBlock->getHideName() ) {
993 $data['HideUser'] = true;
994 }
995
996 $block = $currentBlock;
997 }
998 } else {
999 $logaction = 'block';
1000 }
1001
1002 Hooks::run( 'BlockIpComplete', [ $block, $performer, $priorBlock ] );
1003
1004 # Set *_deleted fields if requested
1005 if ( $data['HideUser'] ) {
1007 }
1008
1009 # Can't watch a rangeblock
1010 if ( $type != DatabaseBlock::TYPE_RANGE && $data['Watch'] ) {
1012 Title::makeTitle( NS_USER, $target ),
1013 $performer,
1015 );
1016 }
1017
1018 # DatabaseBlock constructor sanitizes certain block options on insert
1019 $data['BlockEmail'] = $block->isEmailBlocked();
1020 $data['AutoBlock'] = $block->isAutoblocking();
1021
1022 # Prepare log parameters
1023 $logParams = [];
1024 $logParams['5::duration'] = $data['Expiry'];
1025 $logParams['6::flags'] = self::blockLogFlags( $data, $type );
1026 $logParams['sitewide'] = $block->isSitewide();
1027
1028 if ( $enablePartialBlocks && !$block->isSitewide() ) {
1029 if ( $data['PageRestrictions'] !== '' ) {
1030 $logParams['7::restrictions']['pages'] = explode( "\n", $data['PageRestrictions'] );
1031 }
1032
1033 if ( $data['NamespaceRestrictions'] !== '' ) {
1034 $logParams['7::restrictions']['namespaces'] = explode( "\n", $data['NamespaceRestrictions'] );
1035 }
1036 }
1037
1038 # Make log entry, if the name is hidden, put it in the suppression log
1039 $log_type = $data['HideUser'] ? 'suppress' : 'block';
1040 $logEntry = new ManualLogEntry( $log_type, $logaction );
1041 $logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
1042 $logEntry->setComment( $data['Reason'][0] );
1043 $logEntry->setPerformer( $performer );
1044 $logEntry->setParameters( $logParams );
1045 # Relate log ID to block ID (T27763)
1046 $logEntry->setRelations( [ 'ipb_id' => $block->getId() ] );
1047 $logId = $logEntry->insert();
1048
1049 if ( !empty( $data['Tags'] ) ) {
1050 $logEntry->addTags( $data['Tags'] );
1051 }
1052
1053 $logEntry->publish( $logId );
1054
1055 return true;
1056 }
1057
1068 public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) {
1069 $a = [];
1070 $msg = $lang === null
1071 ? wfMessage( 'ipboptions' )->inContentLanguage()->text()
1072 : wfMessage( 'ipboptions' )->inLanguage( $lang )->text();
1073
1074 if ( $msg == '-' ) {
1075 return [];
1076 }
1077
1078 foreach ( explode( ',', $msg ) as $option ) {
1079 if ( strpos( $option, ':' ) === false ) {
1080 $option = "$option:$option";
1081 }
1082
1083 list( $show, $value ) = explode( ':', $option );
1084 $a[$show] = $value;
1085 }
1086
1087 if ( $a && $includeOther ) {
1088 // if options exist, add other to the end instead of the begining (which
1089 // is what happens by default).
1090 $a[ wfMessage( 'ipbother' )->text() ] = 'other';
1091 }
1092
1093 return $a;
1094 }
1095
1107 public static function parseExpiryInput( $expiry ) {
1108 if ( wfIsInfinity( $expiry ) ) {
1109 return 'infinity';
1110 }
1111
1112 $expiry = strtotime( $expiry );
1113
1114 if ( $expiry < 0 || $expiry === false ) {
1115 return false;
1116 }
1117
1118 return wfTimestamp( TS_MW, $expiry );
1119 }
1120
1126 public static function canBlockEmail( UserIdentity $user ) {
1127 global $wgEnableUserEmail;
1128
1129 return ( $wgEnableUserEmail && MediaWikiServices::getInstance()
1131 ->userHasRight( $user, 'blockemail' ) );
1132 }
1133
1148 public static function checkUnblockSelf( $target, User $performer ) {
1149 if ( is_int( $target ) ) {
1151 } elseif ( is_string( $target ) ) {
1153 }
1154 if ( $performer->getBlock() ) {
1155 if ( $target instanceof User && $target->getId() == $performer->getId() ) {
1156 # User is trying to unblock themselves
1157 if ( MediaWikiServices::getInstance()
1159 ->userHasRight( $performer, 'unblockself' )
1160 ) {
1161 return true;
1162 # User blocked themselves and is now trying to reverse it
1163 } elseif ( $performer->blockedBy() === $performer->getName() ) {
1164 return true;
1165 } else {
1166 return 'ipbnounblockself';
1167 }
1168 } elseif (
1169 $target instanceof User &&
1170 $performer->getBlock() instanceof DatabaseBlock &&
1171 $performer->getBlock()->getBy() &&
1172 $performer->getBlock()->getBy() === $target->getId()
1173 ) {
1174 // Allow users to block the user that blocked them.
1175 // This is to prevent a situation where a malicious user
1176 // blocks all other users. This way, the non-malicious
1177 // user can block the malicious user back, resulting
1178 // in a stalemate.
1179 return true;
1180
1181 } else {
1182 # User is trying to block/unblock someone else
1183 return 'ipbblocked';
1184 }
1185 } else {
1186 return true;
1187 }
1188 }
1189
1197 protected static function blockLogFlags( array $data, $type ) {
1198 $config = RequestContext::getMain()->getConfig();
1199
1200 $blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
1201
1202 $flags = [];
1203
1204 # when blocking a user the option 'anononly' is not available/has no effect
1205 # -> do not write this into log
1206 if ( !$data['HardBlock'] && $type != DatabaseBlock::TYPE_USER ) {
1207 // For grepping: message block-log-flags-anononly
1208 $flags[] = 'anononly';
1209 }
1210
1211 if ( $data['CreateAccount'] ) {
1212 // For grepping: message block-log-flags-nocreate
1213 $flags[] = 'nocreate';
1214 }
1215
1216 # Same as anononly, this is not displayed when blocking an IP address
1217 if ( !$data['AutoBlock'] && $type == DatabaseBlock::TYPE_USER ) {
1218 // For grepping: message block-log-flags-noautoblock
1219 $flags[] = 'noautoblock';
1220 }
1221
1222 if ( $data['DisableEmail'] ) {
1223 // For grepping: message block-log-flags-noemail
1224 $flags[] = 'noemail';
1225 }
1226
1227 if ( $blockAllowsUTEdit && $data['DisableUTEdit'] ) {
1228 // For grepping: message block-log-flags-nousertalk
1229 $flags[] = 'nousertalk';
1230 }
1231
1232 if ( $data['HideUser'] ) {
1233 // For grepping: message block-log-flags-hiddenname
1234 $flags[] = 'hiddenname';
1235 }
1236
1237 return implode( ',', $flags );
1238 }
1239
1246 public function onSubmit( array $data, HTMLForm $form = null ) {
1247 // If "Editing" checkbox is unchecked, the block must be a partial block affecting
1248 // actions other than editing, and there must be no restrictions.
1249 if ( isset( $data['Editing'] ) && $data['Editing'] === false ) {
1250 $data['EditingRestriction'] = 'partial';
1251 $data['PageRestrictions'] = '';
1252 $data['NamespaceRestrictions'] = '';
1253 }
1254 return self::processForm( $data, $form->getContext() );
1255 }
1256
1261 public function onSuccess() {
1262 $out = $this->getOutput();
1263 $out->setPageTitle( $this->msg( 'blockipsuccesssub' ) );
1264 $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
1265 }
1266
1275 public function prefixSearchSubpages( $search, $limit, $offset ) {
1276 $user = User::newFromName( $search );
1277 if ( !$user ) {
1278 // No prefix suggestion for invalid user
1279 return [];
1280 }
1281 // Autocomplete subpage as user list - public to allow caching
1282 return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
1283 }
1284
1285 protected function getGroupName() {
1286 return 'users';
1287 }
1288}
getPermissionManager()
$wgBlockCIDRLimit
Limits on the possible sizes of range blocks.
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
An error page which can definitely be safely rendered using the OutputPage.
Special page which uses an HTMLForm to handle processing.
string null $par
The sub-page of the special page.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:131
setHeaderText( $msg, $section=null)
Set header text, inside the form.
Definition HTMLForm.php:824
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
formatErrors( $errors)
Format a stack of error messages into a single HTML string.
setSubmitDestructive()
Identify that the submit button in the form has a destructive action.
addHeaderText( $msg, $section=null)
Add HTML to the header, inside the form.
Definition HTMLForm.php:802
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:28
A collection of public static functions to play with IP address and IP ranges.
Definition IP.php:67
Internationalisation code.
Definition Language.php:37
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class for creating new log entries and inserting them into the database.
isSitewide( $x=null)
Indicates that the block is a sitewide block.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
getType()
Get the type of target for this particular block.int AbstractBlock::TYPE_ constant,...
MediaWikiServices is the service locator for the application scope of MediaWiki.
static numParam( $num)
Definition Message.php:1038
static suppressUserName( $name, $userId, IDatabase $dbw=null)
static unsuppressUserName( $name, $userId, IDatabase $dbw=null)
A special page that allows users with 'block' right to block users from editing pages and other actio...
int $type
DatabaseBlock::TYPE_ constant.
requiresUnblock()
We allow certain special cases where user is blocked.
preText()
Add header elements like block log entries, etc.
static processForm(array $data, IContextSource $context)
Given the form data, actually implement a block.
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
static validateTarget( $value, User $user)
Validate a block target.
static parseExpiryInput( $expiry)
Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute ("24 May 2034",...
static getTargetAndType( $par, WebRequest $request=null)
Determine the target of the block, and the type of target.
static validateTargetField( $value, $alldata, $form)
HTMLForm field validation-callback for Target field.
bool $requestedHideUser
Whether the previous submission of the form asked for HideUser.
maybeAlterFormDefaults(&$fields)
If the user has already been blocked with similar settings, load that block and change the defaults f...
getFormFields()
Get the HTMLForm descriptor array for the block form.
postText()
Add footer elements to the form.
getDisplayFormat()
Get display format for the form.
static checkUnblockSelf( $target, User $performer)
T17810: blocked admins should not be able to block/unblock others, and probably shouldn't be able to ...
setParameter( $par)
Handle some magic here.
User string $previousTarget
The previous block target.
checkExecutePermissions(User $user)
Checks that the user can unblock themselves if they are trying to do so.
alterForm(HTMLForm $form)
Customizes the HTMLForm a bit.
static getTargetUserTitle( $target)
Get a user page target for things like logs.
User string null $target
User to be blocked, as passed either by parameter (url?wpTarget=Foo) or as subpage (Special:Block/Foo...
static canBlockEmail(UserIdentity $user)
Can we do an email block?
doesWrites()
Indicates whether this special page may perform database writes.
onSubmit(array $data, HTMLForm $form=null)
Process the form on POST submission.
static blockLogFlags(array $data, $type)
Return a comma-delimited list of "flags" to be passed to the log reader for this block,...
static getSuggestedDurations(Language $lang=null, $includeOther=true)
Get an array of suggested block durations from MediaWiki:Ipboptions.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getName()
Get the name of this Special Page.
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getSkin()
Shortcut to get the skin being used for this instance.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
MediaWiki Linker LinkRenderer null $linkRenderer
static search( $audience, $search, $limit, $offset=0)
Do a prefix search of user names and return a list of matching user names.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2364
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:518
getId()
Get the user's ID.
Definition User.php:2335
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:542
const IGNORE_USER_RIGHTS
Definition User.php:83
getUserPage()
Get this user's personal page title.
Definition User.php:4381
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition User.php:2225
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:2200
isAnon()
Get whether the user is anonymous.
Definition User.php:3638
static doWatch(Title $title, User $user, $checkRights=User::CHECK_USER_RIGHTS)
Watch a page.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
const NS_USER
Definition Defines.php:71
const NS_USER_TALK
Definition Defines.php:72
Interface for objects which can provide a MediaWiki context on request.
Interface for objects representing user identity.
$context
Definition load.php:45
$expiryTime
if(!isset( $args[0])) $lang