MediaWiki 1.42.0
SpecialBlock.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Specials;
25
27use HtmlArmor;
28use Language;
61use OOUI\FieldLayout;
62use OOUI\HtmlSnippet;
63use OOUI\LabelWidget;
64use OOUI\Widget;
65use Wikimedia\IPUtils;
66
74
75 private BlockUtils $blockUtils;
76 private BlockPermissionCheckerFactory $blockPermissionCheckerFactory;
77 private BlockUserFactory $blockUserFactory;
78 private DatabaseBlockStore $blockStore;
79 private UserNameUtils $userNameUtils;
80 private UserNamePrefixSearch $userNamePrefixSearch;
81 private BlockActionInfo $blockActionInfo;
82 private TitleFormatter $titleFormatter;
83
87 protected $target;
88
90 protected $type;
91
93 protected $previousTarget;
94
97
99 protected $alreadyBlocked;
100
105 protected $preErrors = [];
106
107 private NamespaceInfo $namespaceInfo;
108
120 public function __construct(
121 BlockUtils $blockUtils,
122 BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
123 BlockUserFactory $blockUserFactory,
124 DatabaseBlockStore $blockStore,
125 UserNameUtils $userNameUtils,
126 UserNamePrefixSearch $userNamePrefixSearch,
127 BlockActionInfo $blockActionInfo,
128 TitleFormatter $titleFormatter,
129 NamespaceInfo $namespaceInfo
130 ) {
131 parent::__construct( 'Block', 'block' );
132
133 $this->blockUtils = $blockUtils;
134 $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory;
135 $this->blockUserFactory = $blockUserFactory;
136 $this->blockStore = $blockStore;
137 $this->userNameUtils = $userNameUtils;
138 $this->userNamePrefixSearch = $userNamePrefixSearch;
139 $this->blockActionInfo = $blockActionInfo;
140 $this->titleFormatter = $titleFormatter;
141 $this->namespaceInfo = $namespaceInfo;
142 }
143
144 public function execute( $par ) {
145 parent::execute( $par );
146
147 if ( $this->getConfig()->get( 'UseCodexSpecialBlock' ) ) {
148 $this->getOutput()->addModules( 'mediawiki.special.block.codex' );
149 }
150 }
151
152 public function doesWrites() {
153 return true;
154 }
155
162 protected function checkExecutePermissions( User $user ) {
163 parent::checkExecutePermissions( $user );
164 // T17810: blocked admins should have limited access here
165 $status = $this->blockPermissionCheckerFactory
166 ->newBlockPermissionChecker( $this->target, $user )
167 ->checkBlockPermissions();
168 if ( $status !== true ) {
169 throw new ErrorPageError( 'badaccess', $status );
170 }
171 }
172
178 public function requiresUnblock() {
179 return false;
180 }
181
187 protected function setParameter( $par ) {
188 // Extract variables from the request. Try not to get into a situation where we
189 // need to extract *every* variable from the form just for processing here, but
190 // there are legitimate uses for some variables
191 $request = $this->getRequest();
192 [ $this->target, $this->type ] = self::getTargetAndTypeInternal( $par, $request );
193 if ( $this->target instanceof UserIdentity ) {
194 // Set the 'relevant user' in the skin, so it displays links like Contributions,
195 // User logs, UserRights, etc.
196 $this->getSkin()->setRelevantUser( $this->target );
197 }
198
199 [ $this->previousTarget, /*...*/ ] = $this->blockUtils
200 ->parseBlockTarget( $request->getVal( 'wpPreviousTarget' ) );
201 $this->requestedHideUser = $request->getBool( 'wpHideUser' );
202 }
203
209 protected function alterForm( HTMLForm $form ) {
210 $form->setHeaderHtml( '' );
211 $form->setSubmitDestructive();
212
213 $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
214 $form->setSubmitTextMsg( $msg );
215
216 $this->addHelpLink( 'Help:Blocking users' );
217
218 // Don't need to do anything if the form has been posted
219 if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
220 // Mimic error messages normally generated by the form
221 $form->addHeaderHtml( (string)new FieldLayout(
222 new Widget( [] ),
223 [
224 'align' => 'top',
225 'errors' => array_map( function ( $errMsg ) {
226 // @phan-suppress-next-line PhanParamTooFewUnpack Should infer non-emptiness
227 return new HtmlSnippet( $this->msg( ...$errMsg )->parse() );
228 }, $this->preErrors ),
229 ]
230 ) );
231 }
232 }
233
234 protected function getDisplayFormat() {
235 return $this->getConfig()->get( 'UseCodexSpecialBlock' ) ? 'codex' : 'ooui';
236 }
237
242 protected function getFormFields() {
243 $conf = $this->getConfig();
244 $blockAllowsUTEdit = $conf->get( MainConfigNames::BlockAllowsUTEdit );
245
246 $this->getOutput()->enableOOUI();
247
248 $user = $this->getUser();
249
250 $suggestedDurations = $this->getLanguage()->getBlockDurations();
251
252 $a = [];
253
254 $a['Target'] = [
255 'type' => 'user',
256 'ipallowed' => true,
257 'iprange' => true,
258 'id' => 'mw-bi-target',
259 'size' => '45',
260 'autofocus' => true,
261 'required' => true,
262 'placeholder' => $this->msg( 'block-target-placeholder' )->text(),
263 'validation-callback' => function ( $value, $alldata, $form ) {
264 $status = $this->blockUtils->validateTarget( $value );
265 if ( !$status->isOK() ) {
266 $errors = $status->getErrorsArray();
267
268 return $form->msg( ...$errors[0] );
269 }
270 return true;
271 },
272 'section' => 'target',
273 ];
274
275 $editingRestrictionOptions = $this->getConfig()->get( 'UseCodexSpecialBlock' ) ?
276 // If we're using Codex, use the option-descriptions feature, which is only supported by Codex
277 [
278 'options-messages' => [
279 'ipb-sitewide' => 'sitewide',
280 'ipb-partial' => 'partial'
281 ],
282 'option-descriptions-messages' => [
283 'sitewide' => 'ipb-sitewide-help',
284 'partial' => 'ipb-partial-help'
285 ],
286 'option-descriptions-messages-parse' => true,
287 ] :
288 // Otherwise, if we're using OOUI, add the options' descriptions as part of their labels
289 [
290 'options' => [
291 $this->msg( 'ipb-sitewide' )->escaped() .
292 new LabelWidget( [
293 'classes' => [ 'oo-ui-inline-help' ],
294 'label' => new HtmlSnippet( $this->msg( 'ipb-sitewide-help' )->parse() ),
295 ] ) => 'sitewide',
296 $this->msg( 'ipb-partial' )->escaped() .
297 new LabelWidget( [
298 'classes' => [ 'oo-ui-inline-help' ],
299 'label' => new HtmlSnippet( $this->msg( 'ipb-partial-help' )->parse() ),
300 ] ) => 'partial',
301 ]
302 ];
303
304 $a['EditingRestriction'] = [
305 'type' => 'radio',
306 'cssclass' => 'mw-block-editing-restriction',
307 'default' => 'sitewide',
308 'section' => 'actions',
309 ] + $editingRestrictionOptions;
310
311 $a['PageRestrictions'] = [
312 'type' => 'titlesmultiselect',
313 'label' => $this->msg( 'ipb-pages-label' )->text(),
314 'exists' => true,
315 'max' => 10,
316 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
317 'default' => '',
318 'showMissing' => false,
319 'excludeDynamicNamespaces' => true,
320 'input' => [
321 'autocomplete' => false
322 ],
323 'section' => 'actions',
324 ];
325
326 $a['NamespaceRestrictions'] = [
327 'type' => 'namespacesmultiselect',
328 'label' => $this->msg( 'ipb-namespaces-label' )->text(),
329 'exists' => true,
330 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
331 'default' => '',
332 'input' => [
333 'autocomplete' => false
334 ],
335 'section' => 'actions',
336 ];
337
338 if ( $conf->get( MainConfigNames::EnablePartialActionBlocks ) ) {
339 $blockActions = $this->blockActionInfo->getAllBlockActions();
340 $a['ActionRestrictions'] = [
341 'type' => 'multiselect',
342 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction mw-block-action-restriction',
343 'options-messages' => array_combine(
344 array_map( static function ( $action ) {
345 return "ipb-action-$action";
346 }, array_keys( $blockActions ) ),
347 $blockActions
348 ),
349 'section' => 'actions',
350 ];
351 }
352
353 $a['CreateAccount'] = [
354 'type' => 'check',
355 'cssclass' => 'mw-block-restriction',
356 'label-message' => 'ipbcreateaccount',
357 'default' => true,
358 'section' => 'details',
359 ];
360
361 if ( $this->blockPermissionCheckerFactory
362 ->newBlockPermissionChecker( null, $user )
363 ->checkEmailPermissions()
364 ) {
365 $a['DisableEmail'] = [
366 'type' => 'check',
367 'cssclass' => 'mw-block-restriction',
368 'label-message' => 'ipbemailban',
369 'section' => 'details',
370 ];
371 }
372
373 if ( $blockAllowsUTEdit ) {
374 $a['DisableUTEdit'] = [
375 'type' => 'check',
376 'cssclass' => 'mw-block-restriction',
377 'label-message' => 'ipb-disableusertalk',
378 'default' => false,
379 'section' => 'details',
380 ];
381 }
382
383 $defaultExpiry = $this->msg( 'ipb-default-expiry' )->inContentLanguage();
384 if ( $this->type === DatabaseBlock::TYPE_RANGE || $this->type === DatabaseBlock::TYPE_IP ) {
385 $defaultExpiryIP = $this->msg( 'ipb-default-expiry-ip' )->inContentLanguage();
386 if ( !$defaultExpiryIP->isDisabled() ) {
387 $defaultExpiry = $defaultExpiryIP;
388 }
389 }
390
391 $a['Expiry'] = [
392 'type' => 'expiry',
393 'required' => true,
394 'options' => $suggestedDurations,
395 'default' => $defaultExpiry->text(),
396 'section' => 'expiry',
397 ];
398
399 $a['Reason'] = [
400 'type' => 'selectandother',
401 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
402 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
403 // Unicode codepoints.
404 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
405 'maxlength-unit' => 'codepoints',
406 'options-message' => 'ipbreason-dropdown',
407 'section' => 'reason',
408 ];
409
410 $a['AutoBlock'] = [
411 'type' => 'check',
412 'label-message' => [
413 'ipbenableautoblock',
415 ],
416 'default' => true,
417 'section' => 'options',
418 ];
419
420 // Allow some users to hide name from block log, blocklist and listusers
421 if ( $this->getAuthority()->isAllowed( 'hideuser' ) ) {
422 $a['HideUser'] = [
423 'type' => 'check',
424 'label-message' => 'ipbhidename',
425 'cssclass' => 'mw-block-hideuser',
426 'section' => 'options',
427 ];
428 }
429
430 // Watchlist their user page? (Only if user is logged in)
431 if ( $user->isRegistered() ) {
432 $a['Watch'] = [
433 'type' => 'check',
434 'label-message' => 'ipbwatchuser',
435 'section' => 'options',
436 ];
437 }
438
439 $a['HardBlock'] = [
440 'type' => 'check',
441 'label-message' => 'ipb-hardblock',
442 'default' => false,
443 'section' => 'options',
444 ];
445
446 // This is basically a copy of the Target field, but the user can't change it, so we
447 // can see if the warnings we maybe showed to the user before still apply
448 $a['PreviousTarget'] = [
449 'type' => 'hidden',
450 'default' => false,
451 ];
452
453 // We'll turn this into a checkbox if we need to
454 $a['Confirm'] = [
455 'type' => 'hidden',
456 'default' => '',
457 'label-message' => 'ipb-confirm',
458 'cssclass' => 'mw-block-confirm',
459 ];
460
461 $this->maybeAlterFormDefaults( $a );
462
463 // Allow extensions to add more fields
464 $this->getHookRunner()->onSpecialBlockModifyFormFields( $this, $a );
465
466 return $a;
467 }
468
474 protected function maybeAlterFormDefaults( &$fields ) {
475 // This will be overwritten by request data
476 $fields['Target']['default'] = (string)$this->target;
477
478 if ( $this->target ) {
479 $status = $this->blockUtils->validateTarget( $this->target );
480 if ( !$status->isOK() ) {
481 $errors = $status->getErrorsArray();
482 $this->preErrors = array_merge( $this->preErrors, $errors );
483 }
484 }
485
486 // This won't be
487 $fields['PreviousTarget']['default'] = (string)$this->target;
488
489 $block = $this->blockStore->newFromTarget( $this->target );
490
491 // Populate fields if there is a block that is not an autoblock; if it is a range
492 // block, only populate the fields if the range is the same as $this->target
493 if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
494 && ( $this->type != DatabaseBlock::TYPE_RANGE
495 || ( $this->target && $block->isBlocking( $this->target ) ) )
496 ) {
497 $fields['HardBlock']['default'] = $block->isHardblock();
498 $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked();
499 $fields['AutoBlock']['default'] = $block->isAutoblocking();
500
501 if ( isset( $fields['DisableEmail'] ) ) {
502 $fields['DisableEmail']['default'] = $block->isEmailBlocked();
503 }
504
505 if ( isset( $fields['HideUser'] ) ) {
506 $fields['HideUser']['default'] = $block->getHideName();
507 }
508
509 if ( isset( $fields['DisableUTEdit'] ) ) {
510 $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed();
511 }
512
513 // If the username was hidden (bl_deleted == 1), don't show the reason
514 // unless this user also has rights to hideuser: T37839
515 if ( !$block->getHideName() || $this->getAuthority()->isAllowed( 'hideuser' ) ) {
516 $fields['Reason']['default'] = $block->getReasonComment()->text;
517 } else {
518 $fields['Reason']['default'] = '';
519 }
520
521 if ( $this->getRequest()->wasPosted() ) {
522 // Ok, so we got a POST submission asking us to reblock a user. So show the
523 // confirm checkbox; the user will only see it if they haven't previously
524 $fields['Confirm']['type'] = 'check';
525 } else {
526 // We got a target, but it wasn't a POST request, so the user must have gone
527 // to a link like [[Special:Block/User]]. We don't need to show the checkbox
528 // as long as they go ahead and block *that* user
529 $fields['Confirm']['default'] = 1;
530 }
531
532 if ( $block->getExpiry() == 'infinity' ) {
533 $fields['Expiry']['default'] = 'infinite';
534 } else {
535 $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() );
536 }
537
538 if ( !$block->isSitewide() ) {
539 $fields['EditingRestriction']['default'] = 'partial';
540
541 $pageRestrictions = [];
542 $namespaceRestrictions = [];
543 foreach ( $block->getRestrictions() as $restriction ) {
544 if ( $restriction instanceof PageRestriction && $restriction->getTitle() ) {
545 $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
546 } elseif ( $restriction instanceof NamespaceRestriction &&
547 $this->namespaceInfo->exists( $restriction->getValue() )
548 ) {
549 $namespaceRestrictions[] = $restriction->getValue();
550 }
551 }
552
553 // Sort the restrictions so they are in alphabetical order.
554 sort( $pageRestrictions );
555 $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
556 sort( $namespaceRestrictions );
557 $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions );
558
560 $actionRestrictions = [];
561 foreach ( $block->getRestrictions() as $restriction ) {
562 if ( $restriction instanceof ActionRestriction ) {
563 $actionRestrictions[] = $restriction->getValue();
564 }
565 }
566 $fields['ActionRestrictions']['default'] = $actionRestrictions;
567 }
568 }
569
570 $this->alreadyBlocked = true;
571 $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( $block->getTargetName() ) ];
572 }
573
574 if ( $this->alreadyBlocked || $this->getRequest()->wasPosted()
575 || $this->getRequest()->getCheck( 'wpCreateAccount' )
576 ) {
577 $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true );
578 }
579
580 // We always need confirmation to do HideUser
581 if ( $this->requestedHideUser ) {
582 $fields['Confirm']['type'] = 'check';
583 unset( $fields['Confirm']['default'] );
584 $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
585 }
586
587 // Or if the user is trying to block themselves
588 if ( (string)$this->target === $this->getUser()->getName() ) {
589 $fields['Confirm']['type'] = 'check';
590 unset( $fields['Confirm']['default'] );
591 $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
592 }
593 }
594
599 protected function preHtml() {
600 $this->getOutput()->addModuleStyles( [ 'mediawiki.special' ] );
601 $this->getOutput()->addModules( [ 'mediawiki.special.block' ] );
602
603 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
604 $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
605
606 $otherBlockMessages = [];
607 if ( $this->target !== null ) {
608 $targetName = $this->target;
609 if ( $this->target instanceof UserIdentity ) {
610 $targetName = $this->target->getName();
611 }
612 // Get other blocks, i.e. from GlobalBlocking or TorBlock extension
613 $this->getHookRunner()->onOtherBlockLogLink(
614 $otherBlockMessages, $targetName );
615
616 if ( count( $otherBlockMessages ) ) {
617 $s = Html::rawElement(
618 'h2',
619 [],
620 $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
621 ) . "\n";
622
623 $list = '';
624
625 foreach ( $otherBlockMessages as $link ) {
626 $list .= Html::rawElement( 'li', [], $link ) . "\n";
627 }
628
629 $s .= Html::rawElement(
630 'ul',
631 [ 'class' => 'mw-blockip-alreadyblocked' ],
632 $list
633 ) . "\n";
634
635 $text .= $s;
636 }
637 }
638
639 return $text;
640 }
641
646 protected function postHtml() {
647 $links = [];
648
649 $this->getOutput()->addModuleStyles( 'mediawiki.special' );
650
651 $linkRenderer = $this->getLinkRenderer();
652 // Link to the user's contributions, if applicable
653 if ( $this->target instanceof UserIdentity ) {
654 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
655 $links[] = $linkRenderer->makeLink(
656 $contribsPage,
657 $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
658 );
659 }
660
661 // Link to unblock the specified user, or to a blank unblock form
662 if ( $this->target instanceof UserIdentity ) {
663 $message = $this->msg(
664 'ipb-unblock-addr',
665 wfEscapeWikiText( $this->target->getName() )
666 )->parse();
667 $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
668 } else {
669 $message = $this->msg( 'ipb-unblock' )->parse();
670 $list = SpecialPage::getTitleFor( 'Unblock' );
671 }
672 $links[] = $linkRenderer->makeKnownLink(
673 $list,
674 new HtmlArmor( $message )
675 );
676
677 // Link to the block list
678 $links[] = $linkRenderer->makeKnownLink(
679 SpecialPage::getTitleFor( 'BlockList' ),
680 $this->msg( 'ipb-blocklist' )->text()
681 );
682
683 // Link to edit the block dropdown reasons, if applicable
684 if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) {
685 $links[] = $linkRenderer->makeKnownLink(
686 $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
687 $this->msg( 'ipb-edit-dropdown' )->text(),
688 [],
689 [ 'action' => 'edit' ]
690 );
691 }
692
693 $text = Html::rawElement(
694 'p',
695 [ 'class' => 'mw-ipb-conveniencelinks' ],
696 $this->getLanguage()->pipeList( $links )
697 );
698
699 $userPage = self::getTargetUserTitle( $this->target );
700 if ( $userPage ) {
701 // Get relevant extracts from the block and suppression logs, if possible
702 $out = '';
703
704 LogEventsList::showLogExtract(
705 $out,
706 'block',
707 $userPage,
708 '',
709 [
710 'lim' => 10,
711 'msgKey' => [
712 'blocklog-showlog',
713 $this->titleFormatter->getText( $userPage ),
714 ],
715 'showIfEmpty' => false
716 ]
717 );
718 $text .= $out;
719
720 // Add suppression block entries if allowed
721 if ( $this->getAuthority()->isAllowed( 'suppressionlog' ) ) {
722 LogEventsList::showLogExtract(
723 $out,
724 'suppress',
725 $userPage,
726 '',
727 [
728 'lim' => 10,
729 'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
730 'msgKey' => [
731 'blocklog-showsuppresslog',
732 $this->titleFormatter->getText( $userPage ),
733 ],
734 'showIfEmpty' => false
735 ]
736 );
737
738 $text .= $out;
739 }
740 }
741
742 return $text;
743 }
744
751 protected static function getTargetUserTitle( $target ): ?PageReference {
752 if ( $target instanceof UserIdentity ) {
753 return PageReferenceValue::localReference( NS_USER, $target->getName() );
754 }
755
756 if ( is_string( $target ) && IPUtils::isIPAddress( $target ) ) {
757 return PageReferenceValue::localReference( NS_USER, $target );
758 }
759
760 return null;
761 }
762
777 public static function getTargetAndType( ?string $par, WebRequest $request = null ) {
778 wfDeprecated( __METHOD__, '1.36' );
779 return self::getTargetAndTypeInternal( $par, $request );
780 }
781
793 private static function getTargetAndTypeInternal( ?string $par, WebRequest $request = null ) {
794 if ( !$request instanceof WebRequest ) {
795 return MediaWikiServices::getInstance()->getBlockUtils()->parseBlockTarget( $par );
796 }
797
798 $possibleTargets = [
799 $request->getVal( 'wpTarget', null ),
800 $par,
801 $request->getVal( 'ip', null ),
802 // B/C @since 1.18
803 $request->getVal( 'wpBlockAddress', null ),
804 ];
805 foreach ( $possibleTargets as $possibleTarget ) {
806 $targetAndType = MediaWikiServices::getInstance()
807 ->getBlockUtils()
808 ->parseBlockTarget( $possibleTarget );
809 // If type is not null then target is valid
810 if ( $targetAndType[ 1 ] !== null ) {
811 break;
812 }
813 }
814 return $targetAndType;
815 }
816
825 public static function processForm( array $data, IContextSource $context ) {
826 $services = MediaWikiServices::getInstance();
827 return self::processFormInternal(
828 $data,
829 $context->getAuthority(),
830 $services->getBlockUserFactory(),
831 $services->getBlockUtils()
832 );
833 }
834
845 private static function processFormInternal(
846 array $data,
847 Authority $performer,
848 BlockUserFactory $blockUserFactory,
849 BlockUtils $blockUtils
850 ) {
851 // Temporarily access service container until the feature flag is removed: T280532
852 $enablePartialActionBlocks = MediaWikiServices::getInstance()
853 ->getMainConfig()->get( MainConfigNames::EnablePartialActionBlocks );
854
855 $isPartialBlock = isset( $data['EditingRestriction'] ) &&
856 $data['EditingRestriction'] === 'partial';
857
858 // This might have been a hidden field or a checkbox, so interesting data
859 // can come from it
860 $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
861
862 // If the user has done the form 'properly', they won't even have been given the
863 // option to suppress-block unless they have the 'hideuser' permission
864 if ( !isset( $data['HideUser'] ) ) {
865 $data['HideUser'] = false;
866 }
867
869 [ $target, $type ] = $blockUtils->parseBlockTarget( $data['Target'] );
870 if ( $type == DatabaseBlock::TYPE_USER ) {
871 $target = $target->getName();
872
873 // Give admins a heads-up before they go and block themselves. Much messier
874 // to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
875 // permission anyway, although the code does allow for it.
876 // Note: Important to use $target instead of $data['Target']
877 // since both $data['PreviousTarget'] and $target are normalized
878 // but $data['target'] gets overridden by (non-normalized) request variable
879 // from previous request.
880 if ( $target === $performer->getUser()->getName() &&
881 ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
882 ) {
883 return [ 'ipb-blockingself', 'ipb-confirmaction' ];
884 }
885
886 if ( $data['HideUser'] && !$data['Confirm'] ) {
887 return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
888 }
889 } elseif ( $type == DatabaseBlock::TYPE_IP ) {
890 $target = $target->getName();
891 } elseif ( $type != DatabaseBlock::TYPE_RANGE ) {
892 // This should have been caught in the form field validation
893 return [ 'badipaddress' ];
894 }
895
896 // Reason, to be passed to the block object. For default values of reason, see
897 // HTMLSelectAndOtherField::getDefault
898 $blockReason = $data['Reason'][0] ?? '';
899
900 $pageRestrictions = [];
901 $namespaceRestrictions = [];
902 $actionRestrictions = [];
903 if ( $isPartialBlock ) {
904 if ( isset( $data['PageRestrictions'] ) && $data['PageRestrictions'] !== '' ) {
905 $titles = explode( "\n", $data['PageRestrictions'] );
906 foreach ( $titles as $title ) {
907 $pageRestrictions[] = PageRestriction::newFromTitle( $title );
908 }
909 }
910 if ( isset( $data['NamespaceRestrictions'] ) && $data['NamespaceRestrictions'] !== '' ) {
911 $namespaceRestrictions = array_map( static function ( $id ) {
912 return new NamespaceRestriction( 0, (int)$id );
913 }, explode( "\n", $data['NamespaceRestrictions'] ) );
914 }
915 if (
916 $enablePartialActionBlocks &&
917 isset( $data['ActionRestrictions'] ) &&
918 $data['ActionRestrictions'] !== ''
919 ) {
920 $actionRestrictions = array_map( static function ( $id ) {
921 return new ActionRestriction( 0, $id );
922 }, $data['ActionRestrictions'] );
923 }
924 }
925 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions, $actionRestrictions );
926
927 if ( !isset( $data['Tags'] ) ) {
928 $data['Tags'] = [];
929 }
930
931 $blockOptions = [
932 'isCreateAccountBlocked' => $data['CreateAccount'],
933 'isHardBlock' => $data['HardBlock'],
934 'isAutoblocking' => $data['AutoBlock'],
935 'isHideUser' => $data['HideUser'],
936 'isPartial' => $isPartialBlock,
937 ];
938
939 if ( isset( $data['DisableUTEdit'] ) ) {
940 $blockOptions['isUserTalkEditBlocked'] = $data['DisableUTEdit'];
941 }
942 if ( isset( $data['DisableEmail'] ) ) {
943 $blockOptions['isEmailBlocked'] = $data['DisableEmail'];
944 }
945
946 $blockUser = $blockUserFactory->newBlockUser(
947 $target,
948 $performer,
949 $data['Expiry'],
950 $blockReason,
951 $blockOptions,
952 $restrictions,
953 $data['Tags']
954 );
955
956 // Indicates whether the user is confirming the block and is aware of
957 // the conflict (did not change the block target in the meantime)
958 $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
959 && $data['PreviousTarget'] !== $target );
960
961 // Special case for API - T34434
962 $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
963
964 $doReblock = !$blockNotConfirmed && !$reblockNotAllowed;
965
966 $status = $blockUser->placeBlock( $doReblock );
967 if ( !$status->isOK() ) {
968 return $status;
969 }
970
971 if (
972 // Can't watch a range block
973 $type != DatabaseBlock::TYPE_RANGE
974
975 // Technically a wiki can be configured to allow anonymous users to place blocks,
976 // in which case the 'Watch' field isn't included in the form shown, and we should
977 // not try to access it.
978 && array_key_exists( 'Watch', $data )
979 && $data['Watch']
980 ) {
981 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
982 $performer->getUser(),
983 Title::makeTitle( NS_USER, $target )
984 );
985 }
986
987 return true;
988 }
989
1001 public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) {
1002 $lang ??= MediaWikiServices::getInstance()->getContentLanguage();
1003 return $lang->getBlockDurations( $includeOther );
1004 }
1005
1015 public static function parseExpiryInput( $expiry ) {
1016 return BlockUser::parseExpiryInput( $expiry );
1017 }
1018
1026 public static function canBlockEmail( UserIdentity $user ) {
1028 ->getBlockPermissionCheckerFactory()
1029 ->newBlockPermissionChecker( null, User::newFromIdentity( $user ) )
1030 ->checkEmailPermissions();
1031 }
1032
1039 public function onSubmit( array $data, HTMLForm $form = null ) {
1040 return self::processFormInternal(
1041 $data,
1042 $this->getAuthority(),
1043 $this->blockUserFactory,
1044 $this->blockUtils
1045 );
1046 }
1047
1052 public function onSuccess() {
1053 $out = $this->getOutput();
1054 $out->setPageTitleMsg( $this->msg( 'blockipsuccesssub' ) );
1055 $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
1056 }
1057
1066 public function prefixSearchSubpages( $search, $limit, $offset ) {
1067 $search = $this->userNameUtils->getCanonical( $search );
1068 if ( !$search ) {
1069 // No prefix suggestion for invalid user
1070 return [];
1071 }
1072 // Autocomplete subpage as user list - public to allow caching
1073 return $this->userNamePrefixSearch
1074 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
1075 }
1076
1077 protected function getGroupName() {
1078 return 'users';
1079 }
1080}
1081
1083class_alias( SpecialBlock::class, 'SpecialBlock' );
getAuthority()
const NS_USER
Definition Defines.php:66
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
An error page which can definitely be safely rendered using the OutputPage.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Base class for language-specific code.
Definition Language.php:63
Defines the actions that can be blocked by a partial block.
Handles the backend logic of blocking users.
Definition BlockUser.php:54
static parseExpiryInput(string $expiry)
Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute ("24 May 2034",...
Backend class for blocking utils.
parseBlockTarget( $target)
From string specification or UserIdentity, get the block target and the type of target.
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|null AbstractBlock::TYPE_ constant,...
Restriction for partial blocks of actions.
Handle database storage of comments such as edit summaries and log reasons.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:206
setHeaderHtml( $html, $section=null)
Set header HTML, inside the form.
Definition HTMLForm.php:964
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
setSubmitDestructive()
Identify that the submit button in the form has a destructive action.
addHeaderHtml( $html, $section=null)
Add HTML to the header, inside the form.
Definition HTMLForm.php:944
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
A class containing constants representing the names of configuration variables.
const AutoblockExpiry
Name constant for the AutoblockExpiry setting, for use with Config::get()
const BlockAllowsUTEdit
Name constant for the BlockAllowsUTEdit setting, for use with Config::get()
const BlockCIDRLimit
Name constant for the BlockCIDRLimit setting, for use with Config::get()
const EnablePartialActionBlocks
Name constant for the EnablePartialActionBlocks setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
static durationParam( $duration)
Definition Message.php:1211
Immutable value object representing a page reference.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
Special page which uses an HTMLForm to handle processing.
string null $par
The sub-page of the special page.
Parent class for all special pages.
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,...
getUser()
Shortcut to get the User executing this instance.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing this instance.
getLanguage()
Shortcut to get user's language.
getName()
Get the canonical, unlocalized name of this special page without namespace.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
A special page that allows users with 'block' right to block users from editing pages and other actio...
static parseExpiryInput( $expiry)
Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute ("24 May 2034",...
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
onSubmit(array $data, HTMLForm $form=null)
Process the form on POST submission.
preHtml()
Add header elements like block log entries, etc.
static canBlockEmail(UserIdentity $user)
Can we do an email block?
int $type
DatabaseBlock::TYPE_ constant.
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.
User string $previousTarget
The previous block target.
alterForm(HTMLForm $form)
Customizes the HTMLForm a bit.
postHtml()
Add footer elements to the form.
requiresUnblock()
We allow certain special cases where user is blocked.
UserIdentity string null $target
User to be blocked, as passed either by parameter (url?wpTarget=Foo) or as subpage (Special:Block/Foo...
getDisplayFormat()
Get display format for the form.
setParameter( $par)
Handle some magic here.
maybeAlterFormDefaults(&$fields)
If the user has already been blocked with similar settings, load that block and change the defaults f...
checkExecutePermissions(User $user)
Check that the user can unblock themselves if they are trying to do so.
doesWrites()
Indicates whether this special page may perform database writes.
static getTargetUserTitle( $target)
Get a user page target for things like logs.
static getTargetAndType(?string $par, WebRequest $request=null)
Get the target and type, given the request and the subpage parameter.
bool $requestedHideUser
Whether the previous submission of the form asked for HideUser.
static processForm(array $data, IContextSource $context)
Given the form data, actually implement a block.
__construct(BlockUtils $blockUtils, BlockPermissionCheckerFactory $blockPermissionCheckerFactory, BlockUserFactory $blockUserFactory, DatabaseBlockStore $blockStore, UserNameUtils $userNameUtils, UserNamePrefixSearch $userNamePrefixSearch, BlockActionInfo $blockActionInfo, TitleFormatter $titleFormatter, NamespaceInfo $namespaceInfo)
getFormFields()
Get the HTMLForm descriptor array for the block form.
execute( $par)
Basic SpecialPage workflow: get a form, send it to the user; get some data back,.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:78
Handles searching prefixes of user names.
UserNameUtils service.
internal since 1.36
Definition User.php:93
newBlockUser( $target, Authority $performer, string $expiry, string $reason='', array $blockOptions=[], array $blockRestrictions=[], $tags=[])
Create BlockUser.
Interface for objects which can provide a MediaWiki context on request.
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
getUser()
Returns the performer of the actions associated with this authority.
A title formatter service for MediaWiki.
Interface for objects representing user identity.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...